
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    const react1 = 
    {
      "text":
      "React 代码规范\n*背景\n整理 React 项目的代码规范\n撰写人：        \n*1 命名规范\n*1.1 组件命名规范\n*1.1.1 [建议] 普通组件命名\n解释：\n*使用首字母大写驼峰（Pascal）形式命名，无论是否使用了 JSX 语法，文件后缀为(j|t)sx\n*通过文件后缀，快速识别代码性质，命名一致则可以让各种工具快速定位文件 (比如 grep、ag)\n示例：\n*// GOOD\n*// MyAwesomeComponent/index.jsx\n*class MyAwesomeComponent extends React.Component {\n*  render() { \n*    return null; \n*  }  // 虽然没有 jsx 语法，但它还是个 React 组件，用 jsx 作为后缀标记文件性质\n*}\n*\n*// BAD\n*// MyAwesomeComponent/index.js\n*class MyAwesomeComponent extends React.Component {\n*  render() { \n*    return null;\n*  }\n*}\n*1.1.2 [强制] 组件名与文件名、目录名保持相同\n解释：\n*文件名、文件夹名应当能体现出组件的功能，以便通过观察文件名即确定使用哪一个组件\n*方便 非 IDE 工具检索文件\n示例：\n*// 假设有个组件叫 InputNumber\n*// GOOD\n*InputNumber/index.tsx\n*InputNumber.tsx\n*\n*// BAD\n*Input/index.tsx\n*Number/index.tsx\n*Input.tsx\n*Number.tsx\n*input-number/index.tsx\n*input-number.tsx\n*1.2 Hooks 命名规范\n*1.2.1 [强制] Hook 组件文件名应以 useXXXX 命名，use 开头驼峰形式\n解释：\n*与 React 对于 Hook 的命名一致，增加识别度\n示例：\n*// GOOD\n*useMyAwesome.js\n*\n*// BAD\n*myAwesomeHook.js\n*1.2.2 [建议] useRef 返回的变量名以 Ref 结尾\n解释：\n*在使用 Hook 时，变量名称极其容易冲突。为了提高变量名的识别度，编码时保持对 ref 使用时的敏感度，用 suffix 来标记变量名\n示例：\n*// GOOD\n*const divRef = useRef(null);\n*\n*// BAD\n*const div = useRef(null);\n*1.3 props 命名规则\n*1.3.1 [建议] 使用 onXxx 形式作为 props 中用于回调的属性名称\n解释：\n*使用统一的命名规则用以区分props中回调和非回调部分的属性，在JSX上可以清晰地看到一个组件向上和向下的逻辑交互\n*对于不用于回调的函数类型的属性，使用动词作为属性名称\n示例：\n*// onClick作为回调以on开头，renderText非回调函数则使用动词\n*const Label = ({onClick, renderText}) =\u003e \u003cspan onClick={onClick}\u003e{renderText()}\u003c/span\u003e;\n*1.3.2 [建议] 如果一个函数是一个自渲染函数（返回的是 ReactNode 类型），使用 renderXXX 命名\n解释：\n*与其他函数区分开，通过 prefix 快速识别出函数是干什么的\n示例：\n*// GOOD\n*class MyComponent extends React.Component {\n*  render() {\n*    const { mode } = this.props;\n*    return (\n*      \u003c\u003e\n*        \u003cHeader /\u003e\n*        \u003cContent\u003e\n*        {mode === 'HORIZONTAL'\n*          ? this.renderHorizontalContent()\n*          : this.renderVerticalContent()\n*        }\n*        \u003c/Content\u003e\n*        \u003cFooter /\u003e\n*      \u003c/\u003e\n*    );\n*  }\n*  renderHorizontalContent = () =\u003e {}\n*  renderVerticalContent = () =\u003e {}\n*}\n*\n*// BAD\n*class MyComponent extends React.Component {\n*  render() {\n*    const { mode } = this.props;\n*    return (\n*      \u003c\u003e\n*        \u003cHeader /\u003e\n*        \u003cContent\u003e\n*        {mode === 'HORIZONTAL'\n*            ? this.horizontalContent()\n*            : this.verticalContent()\n*        }\n*        \u003c/Content\u003e\n*        \u003cFooter /\u003e\n*      \u003c/\u003e\n*    );\n*  }\n*  horizontalContent = () =\u003e {}\n*  verticalContent = () =\u003e {}\n*}\n*1.4 组件方法命名规则\n*1.4.1 [建议] 作为组件方法的事件处理函数以具备业务含义的词作为名称，使用 handleXxx 形式命名\n示例：\n*// Good\n*class Form {\n*  handleCollectAndSubmitData = () =\u003e {\n*    const { onSubmit } = this.props;\n*    const { name, age } = this.state;\n*    const data = {\n*      name,\n*      age,\n*    };\n*    onSubmit(data);\n*  };\n*  handleAyncName = () =\u003e {\n*    // ...\n*  };\n*  handleSyncAge = () =\u003e {\n*    // ...\n*  };\n*  render() {\n*    return (\n*      \u003cdiv\u003e\n*        \u003clabel\u003e\n*          姓名：\n*          \u003cinput type=\"text\" onChange={this.handleAsyncName} /\u003e\n*        \u003c/label\u003e\n*        \u003clabel\u003e\n*          年龄：\n*          \u003cinput type=\"number\" onChange={this.handleSyncAge} /\u003e\n*        \u003c/label\u003e\n*        \u003cbutton type=\"button\" onClick={this.handleCollectAndSubmitData}\u003e\n*          提交\n*        \u003c/button\u003e\n*      \u003c/div\u003e\n*    );\n*  }\n*}\n*2 组件实现规范\n*2.1 React API、生命周期相关规范\n*2.1.1 [强制] 不要使用 UNSAFE_componentWillMount、UNSAFE_componentWillReceiveProps、UNSAFE_componentWillUpdate 的生命周期\n解释：\n*UNSAFE_xxxx 即将在 17.x 被废弃，这几个状态属于视图和当前 state 不匹配的状态（比如 componentWillUpdate，此时页面显示和当前 state 不一致），在这几个生命周期取值、处理数据，较易发生难以察觉的问题\n示例：\n*// GOOD\n*componentDidMount() {\n*  this.componentDidUpdate();\n*}\n*componentDidUpdate(prevProps) {\n*  const { id } = this.props;\n*  if (!prevProps || id !== prevProps.id) {\n*    this.fetchData(id);\n*  }\n*}\n*\n*// BAD\n*componentWillMount() {\n*  this.componentWillUpdate();\n*}\n*componentWillUpdate(nextProps) {\n*  const { id } = this.props;\n*  if (!nextProps || nextProps.id !== id) {\n*    this.fetchData(nextProps.id);\n*  }\n*}\n*2.1.2 [建议] 除了 componentDidMount componentWillUnmount componentDidUpdate 三个生命周期，禁止出现 window、document、navigator 这类浏览器的全局变量\n解释：\n*componentDidMount componentWillUnmount componentDidUpdate 不会在 SSR 时执行\n*2.1.3 [强制] 禁止在 componentWillUnmount 时 setState\n*2.1.4 [强制] 禁止在 componentDidMount componentDidUpdate 时没有判断条件地调用 setState\n解释：\n*componentDidUpdate 会造成死循环\n*2.1.5 [强制] 禁止直接修改 this.state\n示例：\n*// GOOD\n*this.setState({ foo: 'foo', baz: 'baz' });\n*this.setState(s =\u003e ({ ...s, foo: 'foo', baz: 'baz' }));\n*\n*// BAD\n*this.state.foo = 'foo';\n*Object.assign(this.state, { baz: 'baz' });\n*this.setState({});\n*2.1.6 [强制] 禁止在 getDerivedStateFromProps 中，没有判断条件地返回特定 state，且至少返回 null\n解释：\n*避免滥用 getDerivedStateFromProps。一般此 api 是用来代替 UNSAFE_componentWillReceiveProps 这个生命周期，处理非完全受控组件内部状态和外部状态同步\n示例：\n*// GOOD\n*static getDerivedStateFromProps(props, state) {\n*  if (props.defaultValue === undefined \u0026\u0026 props.value) {\n*    // 假设组件如果有 defaultValue，说明是个非受控组件，渲染以 state 为准\n*    // 如果有 value，说明组件是个受控组件，渲染以 props 为准\n*    return {\n*      ...state,\n*      value: props.value,\n*    }\n*  }\n*  return null;\n*}\n*\n*// BAD\n*static getDerivedStateFromProps(props, state) {\n*  return {\n*    id: props.id,\n*    xxx: props.xxx,\n*  };\n*}\n*// 部分任何情况的映射，不如以 getter 形式来获取\n*get computedProps() {\n*  const { id, xxx } = this.props;\n*  return { id, xxx };\n*}\n*2.1.7 [建议] 禁止 this.setState 中使用 this.state\n解释：\n*this.state 不是同步更新的，可能导致非预期结果\n示例：\n*function increment() {\n*  const { value } = this.state;\n*  this.setState({ value: value + 1 });\n*}\n*// 如果两次调用同时发生，结果相当于\n*setState({ value: 1 + 1 });\n*setState({ value: 1 + 1 });\n*// 可以使用回调函数避免此问题\n*function increment() {\n*  this.setState(prevState =\u003e ({ value: prevState.value + 1 }));\n*}\n*// 这样快速调用时，结果相当于\n*setState({ value: 1 + 1 });\n*setState({ value: 2 + 1 });\n*\n*// GOOD\n*this.setState(prevState =\u003e ({ value: prevState.value + 1 }));\n*\n*// BAD\n*this.setState({ value: this.state.value + 1 });\n*2.1.8 [强制] 禁止使用已废弃的方法或属性\n解释：\n*已废弃或不推荐使用的 API 如下:\n*componentWillMount、componentWillUpdate、componentWillReceiveProps、replaceState、getInitialState、isMounted、createClass、mixin、createFactory、ReactDOM.findDOMNode\n*V17 即将废弃的 API 如下:\n*UNSAFE_componentWillMount、UNSAFE_componentWillUpdate、UNSAFE_componentWillReceiveProps\n*2.1.9 [强制] 禁止在 PureComponent 中设置 shouldComponentUpdate 方法\n解释：\n*如果一定要设置此方法，考虑使用 Component\n*React的相关Issue\n*在React的实现中，PureComponent 并不直接实现 shouldComponentUpdate，而是添加一个 isReactPureComponent 的标记，由 CompositeComponent 通过识别这个标记实现相关的逻辑。因此在 PureComponent 上自定义 shouldComponentUpdate 并无法享受 super.shouldComponentUpdate 的逻辑复用，也会使得这个继承关系失去意义\n*2.1.10 [强制] 禁止使用ReactDOM.render方法的返回值\n示例：\n*// GOOD\n*ReactDOM.render(\u003cApp /\u003e, document.body, doSomethingWithInst);\n*\n*// BAD\n*const inst = ReactDOM.render(\u003cApp /\u003e, document.body);\n*doSomethingWithInst(inst);\n*2.1.11 [强制] 禁止使用字符串ref\n解释：\n*官方已废弃此用法 详细\n示例：\n*// GOOD\n*this.myRef = React.createRef();\n*\u003cinput ref={this.myRef} /\u003e\n*\n*const myRef = React.useRef(null);\n*\u003cinput ref={myRef} /\u003e\n*\n*this.getRef = (dom) =\u003e this.myInput = dom;\n*\u003cinput ref={this.getRef} /\u003e\n*\n*// BAD\n*\u003cinput ref=\"inputRef\" /\u003e\n*2.1.12 [建议] 生命周期的书写顺序\n*static displayName\n*static propTypes\n*static contextTypes\n*static defaultProps\n*其它静态的属性\n*state\n*constructor\n*public 方法（非生命周期的方法，组件向外部暴露的方法，外部可以通过 ref 调用的方法） \n*getChildContext\n*componentWillMount (react 16 后不应该使用)\n*componentDidMount\n*componentWillReceiveProps (react 16 后不应该使用)\n*shouldComponentUpdate\n*componentWillUpdate (react 16 后不应该使用)\n*componentDidUpdate\n*componentWillUnmount\n*事件处理函数，如： handleClickSubmit() or handleChangeDescription()\n*自定义方法，不属于17、19、20的方法或无法归类的方法，如：fetchData()\n*render 方法的 getter，如： getSelectReason() or getFooterContent()\n*某些抽取的部分 render 方法，如： renderNavigation() or renderProfilePicture()\n*render\n解释：\n*按照 react 生命周期的执行顺序排序，render 函数放在最下面容易查找阅读\n示例：\n*class A {\n*  componentDidMount() {}\n*  xxx() {}\n*  render() {}\n*}\n*2.1.13 [建议]TSX中，组件方法需要声明private、protected，public 无需书写，遵循TS规范\n示例：\n*// Good\n*class B {\n*  componentDidMount() {}\n*  private handleButtonClick() {}\n*  render() {}\n*}\n*2.1.14 [建议] 每个组件都要写 propTypes，TSX 中可以省略\n*2.2 JSX 相关规范\n*2.2.1 [强制] 没有子节点的非DOM组件使用自闭合语法\n解释：\n*对于DOM节点，按照HTML编码规范相关规则进行闭合，其中void element使用自闭合语法。\n示例：\n*// Good\n*\u003cFoo /\u003e\n*\n*// Bad\n*\u003cFoo\u003e\u003c/Foo\u003e\n*2.2.2 [强制] 保持起始和结束标签在同一层缩进\n解释：\n*对于标签前面有其它语句（如return的情况，使用括号进行换行和缩进）。\n示例：\n*// Good\n*class Message {\n*  render() {\n*    return (\n*      \u003cdiv\u003e\n*        \u003cspan\u003eHello World\u003c/span\u003e\n*      \u003c/div\u003e\n*    );\n*  }\n*}\n*\n*// Bad\n*class Message {\n*  render() {\n*    return \u003cdiv\u003e\n*        \u003cspan\u003eHello World\u003c/span\u003e\n*      \u003c/div\u003e\n*    ;\n*  }\n*}\n对于直接return的函数组件，可以直接使用括号而省去大括号和return关键字：\n*const Message = () =\u003e (\n*  \u003cdiv\u003e\n*    \u003cspan\u003eHello World\u003c/span\u003e\n*  \u003c/div\u003e\n*);\n*2.2.3 [强制] JSX中的props缩进为2个空格，且props与value之间的等号前后禁止有空格\n解释：\n*提高代码可读性。\n示例：\n*// Good\n*// 2 spaces indentation, no space around prop and value\n*\u003cHello\n*  firstName={firstName}\n*  lastName={lastName}\n*/\u003e\n*\n*// Bad\n*// no indentation\n*\u003cHello firstName=\"John\"\n*/\u003e\n*// 1 tab indentation\n*\u003cHello\n*  firstName=\"John\"\n*/\u003e\n*// has space around prop and value\n*\u003cHello firstName = {firstName} /\u003e;\n*\u003cHello firstName ={firstName} /\u003e;\n*\u003cHello firstName= {firstName} /\u003e;\n*2.2.4 [强制] 自闭合标签的/\u003e前添加一个空格\n示例：\n*// Good\n*\u003cFoo bar=\"bar\" /\u003e\n*\n*// Bad\n*\u003cFoo bar=\"bar\"/\u003e\n*\u003cFoo bar=\"bar\"  /\u003e\n*2.2.5 [强制] 以字符串字面量作为值的属性使用双引号\"，在其它类型表达式中的字符串使用单引号'\n解释：\n*对齐 html 规范\n示例：\n*// GOOD\n*\u003cComponent prop=\"value\" /\u003e\u003cFoo style={{ width: '20px' }} /\u003e\n*\n*// BAD\n*\u003cComponent prop='value' /\u003e\u003cFoo style={{ width: \"20px\" }} /\u003e\n*2.2.6 [强制] 对于值为true的属性，省去值部分\n解释：\n*符合html的规范，如: \u003cradio checked\u003e选中\u003c/radio\u003e\n示例：\n*// Good\n*\u003cFoo visible /\u003e\n*\n*// Bad\n*\u003cFoo visible={true} /\u003e\n*2.2.7 [强制] 避免在JSX的属性值中直接使用对象或函数表达式\n解释：\n*PureComponent使用shallowEqual对props和state进行比较来决定是否需要渲染，而在JSX的属性值中使用对象、函数表达式会造成每一次的对象引用不同，从而shallowEqual会返回false，导致不必要的渲染。\n示例：\n*// Good\n*class WarnButton {\n*  alertMessage = () =\u003e {\n*    const { message } = this.props;\n*    alert(message);\n*  };\n*\n*  render() {\n*    return (\n*      \u003cbutton type=\"button\" onClick={this.alertMessage}\u003e\n*        提示\n*      \u003c/button\u003e\n*    );\n*  }\n*}\n*\n*// Bad\n*class WarnButton {\n*  alertMessage(message) {\n*    alert(message);\n*  }\n*\n*  render() {\n*    const { message } = this.props;\n*    return (\n*      \u003cbutton\n*        type=\"button\"\n*        onClick={() =\u003e this.alertMessage(message)}\n*      \u003e\n*        提示\n*      \u003c/button\u003e\n*    );\n*  }\n*}\n*2.2.8 [强制] 使用button组件时，必须包含type属性\n解释：\n*在 \u003cform\u003e 标签中的 button 属性 type 默认值为 submit，会导致非预期的页面重载\n示例：\n*// GOOD\n*const Hello = \u003cbutton type=\"button\"\u003eHello\u003c/button\u003e;\n*const Hello = \u003cbutton type=\"submit\"\u003eHello\u003c/button\u003e;\n*const Hello = \u003cbutton type=\"reset\"\u003eHello\u003c/button\u003e;\n*\n*// BAD\n*const Hello = \u003cbutton\u003eHello\u003c/button\u003e;\n*const Hello = \u003cbutton type=\"foo\"\u003eHello\u003c/button\u003e;\n*2.2.9 [建议] \u003ca\u003e 标签如果指定 target=\"_blank\"，如果 href 是完整的 http 地址，必须同时指定 rel=\"noreferrer noopener\"\n解释：\n*防止追踪和通过 window.opener 访问、更改 location 详情\n*补：在未开启 Site Isolation 策略的游览器下，防止跳转的页面和当前页面共享一个渲染进程，让两个页面的渲染进程隔离 by  （2020/10/10 补充：实测 Chrome 67、FireFox 58 之后不存在此问题，Safari 待测试 TODO:   ）\n*复用渲染进程的优点：\n*共用渲染进程可复用共同资源、节省创建进程的开支（Chrome 67 之后已不再使用非同源站点的进程复用，故删掉此优点）\n示例：\n*// GOOD\n*\u003ca target=\"https://toutiao.com\" rel=\"noreferrer noopener\"\u003eJump to other site\u003c/a\u003e\n*\u003ca target=\"/contact\"\u003eJump to the same site\u003c/a\u003e\n*\n*// BAD\n*\u003ca target=\"https://toutiao.com\"\u003eJump to other site\u003c/a\u003e\n*2.2.10 [强制] 无状态函数组件（FC）中，不允许使用 this\n解释：\n*在改写class组件到function组件的过程中，可能导致遗漏\n示例：\n*// GOOD\n*function Foo(props) {\n*  const { bar } = props;\n*  return \u003cdiv\u003e{bar}\u003c/div\u003e;\n*}\n*\n*// BAD\n*function Foo(props) {\n*  const { bar } = this.props;\n*  return \u003cdiv\u003e{bar}\u003c/div\u003e;\n*}\n*2.2.11 [强制] 组件必须提供命名，高阶组件生成的组件需要指定 displayName，名字应含有高阶组件的功能和所包装组件的原名称\n解释：\n*如果不设置的话，debug tools中会显示Unknown，不方便调试，高阶组件由于是个函数，还是遵循首字母小写驼峰形式，比如：withXXXX、asXXXX。\n*PS：\n*如果一个匿名函数赋值给某个变量，那么这个匿名函数的 name 属性会自动变为该变量名字，所以无需担心赋值语句右值是匿名函数在 debug tools 显示 Unknown 的情况\n示例：\n*// GOOD\n*export default class Hello extends Component {}\n*\n*const Hello = () =\u003e {};\n*export default Hello;\n*\n*export const World = () =\u003e {};\n*\n*// 对于HoC\n*const asPureComponent = Component =\u003e {\n*  const componentName =\n*    Component.displayName || Component.name || \"UnknownComponent\";\n*  return class extends PureComponent {\n*    // 需要在这里声明displayName\n*    static displayName = `asPure(${componentName})`;\n*    render() {\n*      return \u003cComponent {...this.props} /\u003e;\n*    }\n*  };\n*};\n*\n*// BAD\n*export default class extends Component {}\n*export default () =\u003e {};\n*2.2.12 [强制] 禁止使用数组的 index 作为 key\n解释：\n*key 最好是唯一的，如果使用 index 作为 key，在排序或队首插入元素时，将导致不必要的计算，影响性能\n*什么情况下禁用\n*当确认列表数据顺序不变时，可禁用此条件\n示例：\n*// GOOD\n*things.map((thing) =\u003e (\n*  \u003cHello key={thing.id} /\u003e\n*));\n*\n*// BAD\n*things.map((thing，index) =\u003e (\n*  \u003cHello key={index} /\u003e\n*));\n*2.2.13 [强制] jsx、tsx 文件中必须引用 react\n解释：\n*使用 JSX 语法隐式地依赖当前环境下有 React，如果不显式引入，可能导致运行时 React 没有定义的报错。(比如，一开始是 webpack 设置 alias 全局引入 React，后来换成打到 bundle 里去，如果没有显式引入 React，就会报错)\n*什么时候可以禁用此条规则\n*在项目构建工具有使用 babel-plugin-react-require 插件时可以禁用\n示例：\n*// GOOD\n*import React from 'react';\n*export const StatelessComponent = () =\u003e \u003ch1\u003eHello world\u003c/h1\u003e;\n*\n*// BAD\n*export const StatelessComponent = () =\u003e \u003ch1\u003eHello world\u003c/h1\u003e;\n*// 上面的代码编译后会变为\n*React.createElement('h1', null, 'Hello world'); // 引入了 React 依赖\n*2.2.14 [强制] 禁止在使用了 dangerouslySetInnerHTML 的组件内添加 children\n解释：\n*使用 dangerouslySetInnerHTML 属性与 children 不可同时设置。\n示例：\n*// Good\n*\u003cdiv dangerouslySetInnerHTML={{ __html: 'HTML' }} /\u003e\n*\u003cHello dangerouslySetInnerHTML={{ __html: 'HTML' }} /\u003e\n*\u003cdiv\u003e\n*  Children\n*\u003c/div\u003e\n*\u003cHello\u003e\n*  Children\n*\u003c/Hello\u003e\n*React.createElement('div', { dangerouslySetInnerHTML: { __html: 'HTML' } });\n*React.createElement('Hello', { dangerouslySetInnerHTML: { __html: 'HTML' } });\n*React.createElement('div', {}, 'Children');\n*React.createElement('Hello', {}, 'Children');\n*\n*// Bad\n*\u003cdiv dangerouslySetInnerHTML={{ __html: 'HTML' }}\u003e\n*  Children\n*\u003c/div\u003e\n*\u003cHello dangerouslySetInnerHTML={{ __html: 'HTML' }}\u003e\n*  Children\n*\u003c/Hello\u003e\n*React.createElement('div', { dangerouslySetInnerHTML: { __html: 'HTML' } }, 'Children');\n*React.createElement('Hello', { dangerouslySetInnerHTML: { __html: 'HTML' } }, 'Children');\n*2.2.15 [强制] JSX.Element 如果有 style 属性，值必须是对象\n示例：\n*// GOOD\n*\u003cdiv style={{ color: 'red' }} /\u003e\n*\u003cHello style={{ color: 'red' }} /\u003econst styles = { color: 'red' };\n*\u003cdiv style={styles} /\u003e\n*React.createElement('div', { style: { color: 'red' } });\n*\n*// BAD\n*\u003cdiv style=\"color: 'red'\" /\u003e\n*\u003cHello style={true} /\u003e\n*const styles = true;\n*\u003cdiv style={styles} /\u003e\n*React.createElement('div', { style: 'color: red;' });\n*2.2.16 [建议] 对于多属性需要换行，从第一个属性开始，每个属性一行\n示例：\n*// Good\n*// 没有子节点\n*\u003cSomeComponent\n*  longProp={longProp}\n*  anotherLongProp={anotherLongProp}\n*/\u003e\n*\n*// 有子节点\n*\u003cSomeComponent\n*  longProp={longProp}\n*  anotherLongProp={anotherLongProp}\n*\u003e\n*  \u003cSomeChild /\u003e\n*  \u003cSomeChild /\u003e\n*\u003c/SomeComponent\u003e\n*\n*// Bad\n*// 2 props on the same line\n*\u003cSomeComponent\n*  longProp={longProp} anotherLongProp={anotherLongProp}\n*/\u003e\n*2.2.17 [建议] 将JSX的层级控制在3层以内\n解释：\n*JSX提供了基于组件的便携的复用形式，因此可以通过将结构中的一部分封装为一个函数组件来很好地拆分大型复杂的结构。层次过深的结构会带来过多缩进、可读性下降等缺点。如同控制函数内代码行数和分支层级一样，对JSX的层级进行控制可以有效提升代码的可维护性。\n示例：\n*// Good\n*const Header = ({title, subtitle}) =\u003e (\n*  \u003cheader\u003e\n*    \u003ch3\u003e{title}\u003c/h3\u003e\n*    \u003cspan\u003e{subtitle}\u003c/span\u003e\n*  \u003c/header\u003e\n*);\n*\n*const Content = ({content}) =\u003e \u003csection\u003e{content}\u003c/section\u003e;\n*\n*const Footer = ({author, postTime}) =\u003e (\n*  \u003cfooter\u003e\n*    \u003cspan\u003e{author}\u003c/span\u003e@\u003ctime\u003e{postTime}\u003c/time\u003e\n*  \u003c/footer\u003e\n*);\n*\n*const Item = item =\u003e (\n*  \u003cdiv\u003e\n*    \u003cHeader {...item} /\u003e\n*    \u003cContent {...item} /\u003e\n*    \u003cFooter {...item} /\u003e\n*  \u003c/div\u003e\n*);\n*\n*const List = ({ items }) =\u003e \u003cul\u003e{items.map(Item)}\u003c/ul\u003e;\n*\n*// Bad\n*const List = ({ items }) =\u003e (\n*  \u003cul\u003e\n*    {items.map(item =\u003e (\n*      \u003cli\u003e\n*        \u003cheader\u003e\n*          \u003ch3\u003e{item.title}\u003c/h3\u003e\n*          \u003cspan\u003e{item.subtitle}\u003c/span\u003e\n*        \u003c/header\u003e\n*        \u003csection\u003e{item.content}\u003c/section\u003e\n*        \u003cfooter\u003e\n*          \u003cspan\u003e{item.author}\u003c/span\u003e@\u003ctime\u003e{item.postTime}\u003c/time\u003e\n*        \u003c/footer\u003e\n*      \u003c/li\u003e\n*    ))}\n*  \u003c/ul\u003e\n*);\n*2.3 成员属性、成员函数规则\n*2.3.1 [强制] 禁止使用属性访问的方式使用其他组件的 propTypes\n解释：\n*这是为了在生产环境用 babel-plugin-transform-react-remove-prop-types 把 propTypes 相关代码去掉\n示例：\n*// GOOD\n*import SomeComponent, { propTypes as someComponentPropTypes } from './SomeComponent';\n*\n*// BAD\n*import SomeComponent from './SomeComponent';\n*SomeComponent.propTypes;\n*const { propTypes } = SomeComponent;\n*SomeComponent['propTypes'];\n*2.3.2 [强制] 为props中所有非必传属性设置默认值，对于propTypes设置为 isRequired 的属性不需要设置默认值，优先使用解构默认值的方式设置默认值\n解释：\n*既无default也没有设置required容易产生bug。\n*defaultProps 正在被官方弃用，所以建议优先使用解构默认值的方式来代替 defaultProps\n*https://github.com/facebook/react/pull/16210\n*https://medium.com/@matanbobi/react-defaultprops-is-dying-whos-the-contender-443c19d9e7f1\n示例：\n*// GOOD\n*class ClassComponent extends React.Component {\n*  static propTypes = {\n*    foo: PropTypes.string,\n*    bar: PropTypes.string.isRequired,\n*  };\n*  render() {\n*    const { foo = '', bar } = this.props; // 解构对象时赋予默认值\n*    // ...\n*  }\n*}\n*\n*FunctionComponent.propTypes = {\n*  foo: PropTypes.string,\n*  bar: PropTypes.string.isRequired,\n*};\n*function FunctionComponent(props) {\n*  const { foo: '', bar } = props;\n*  // ...\n*}\n*\n*// BAD\n*class ClassComponent extends React.Component {\n*  static propTypes = {\n*    // foo 已经定义在 defaultProps 中，不需要 isRequired\n*    foo: PropTypes.string.isRequired,\n*    // bar 没有定义在 defaultProps 中，需要加上 isRequired\n*    bar: PropTypes.string,\n*  };\n*  static defaultProps = {\n*    foo: \"\",\n*  };\n*}\n*\n*FunctionComponent.propTypes = {\n*  // foo 已经定义在 defaultProps 中，不需要 isRequired\n*  foo: PropTypes.string.isRequired,\n*  // bar 没有定义在 defaultProps 中，需要加上 isRequired\n*  bar: PropTypes.string,\n*};\n*FunctionComponent.defaultProps = {\n*  foo: \"\",\n*};\n*2.3.3 [强制] 使用 Class Property 语法声明 propTypes、contextTypes、defaultProps 和 state\n解释：\n*逻辑更清晰，一目了然\n示例：\n*// GOOD\n*class MyComponent extends React.Component {\n*  static propTypes = {};\n*  static defaultProps = {};\n*  state = {\n*    foo: this.props.foo,\n*    baz: \"baz\",\n*    ctx: this.context.value,\n*  };\n*}\n*\n*// BAD\n*class MyComponent extends React.Component {\n*  constructor(props, ctx) {\n*    this.state = {\n*      foo: props.foo,\n*      baz: \"baz\",\n*      ctx: ctx.value,\n*    };\n*  }\n*}\n*MyComponent.propTypes = {};\n*MyComponent.defaultProps = {};\n*2.3.4 [强制] component 必须有 render 方法，且 render 方法必须有返回\n*2.3.5 [强制] 使用解构获取 state、props、context 中的属性\n解释：\n*使代码可读性更高，更简洁\n示例：\n*// GOOD\n*render {\n*  const { props1, props2 } = this.props;\n*  const { state1, state2 } = this.state;\n*  ...\n*}\n*\n*// BAD\n*render {\n*  const props1 = this.props.props1;\n*  const props2 = this.props.props2;\n*  const state1 = this.props.state1;\n*  const state2 = this.props.state2;\n*  ...\n*}\n*2.4 Hooks 规范\n*2.4.1 [强制] 自定义 Hook 中必须直接或间接包含至少一个 React Hook\n解释：\n*如果不含任何 React Hook，说明这不是个 Hook 组件\n*2.4.2 [强制] 只在顶层调用 Hooks。不要在循环、条件语句或嵌套函数中调用 Hooks\n解释：\n*不要在循环、条件语句或嵌套函数中调用 Hooks，而只在 React 函数顶层调用 Hooks。Hook 是一个工作流程，在 React 中是一个链表数据结构，遵循这一规则，能够保证每次组件渲染时 Hooks 按同一顺序调用\n示例：\n*// GOOD\n*useEffect(() =\u003e {\n*  if (condition === 'a') {\n*    // do something when a\n*  }\n*}, []);\n*\n*// BAD\n*if (condition === 'a') {\n*  useEffect(() =\u003e {\n*    // do something when a\n*  }, []);\n*}\n*2.4.3 [建议] useEffect、useMemo、useCallback 的 deps 参数必须包含所有 callback 方法中用到的变量\n解释：\n*避免 callback 执行的上下文变量是过期的变量，而非预想中的变量\n示例：\n*// GOOD\n*const [state, setState] = useState({});\n*useEffect(() =\u003e {\n*  doSomething(state.a);\n*  doOtherThing(state.b);\n*}, [state.a, state.b]);\n*\n*// BAD\n*const [state, setState] = useState({});\n*useEffect(() =\u003e {\n*  doSomething(state.a);\n*  doOtherThing(state.b);\n*}, [state.a /*, state.b*/]);\n*2.4.4 [建议] useRef 的 React.Ref 对象，在访问时，不要用 someRef.current 访问，而应该先取到某个变量中访问\n解释：\n*ref 因为是个 mutable 对象，当在多个 effect 中设置值时，如果 effect 从 ref.currrent 中取值，会得到其他 effect 处理后的结果，这样会引入 effect 顺序依赖。所以，在访问 ref.current 前，直接用一个变量存取最后一次渲染的 ref 状态，避免在受到其他 effect 中变更的影响\n示例一：FunctionComponent Render 过程中的 reference 访问一致性处理方式\n*// GOOD\n*const prevPropsRef = useRef();\n*const prevProps = someRef.current;\n*useEffect(() =\u003e {\n*  prevPropsRef.current = props;  // 这行会修改 prevPropsRef 的值\n*}, [props]);\n*useEffect(() =\u003e {\n*  // 虽然前一个 Effect 修改了 prevPropsRef 的值，但是这里访问的是之前存取的变量，不受影响\n*  if (prevProps \u0026\u0026 prevProps.id !== props.id) {\n*    // do something\n*  }\n*}, [props]);\n*\n*// BAD\n*const prevPropsRef = useRef();\n*useEffect(() =\u003e {\n*  prevPropsRef.current = props;  // 这行会修改 prevPropsRef 的值\n*}, [props]);\n*useEffect(() =\u003e {\n*  // 因为上一个 Effect 会在这个之前执行并且把 prevPropsRef.current 更新成最新的 props\n*  // 所以此处永远为 true\n*  if (prevPropsRef.current \u0026\u0026 prevPropsRef.current.id !== props.id) {\n*    // do something\n*  }\n*}, [props]);\n示例二：useEffect 中 reference 访问一致性处理方式\n*// GOOD\n*const divRef = useRef(null);\n*useEffect(() =\u003e {\n*  const div = divRef.current\n*  if (!div) {\n*    return;\n*  }\n*  const someAsyncMethod = async() =\u003e {\n*    await a_few_moments_later();\n*    doSomethingWith(div);\n*  }\n*  someAsyncMethod();\n*\n*  const handler = () =\u003e xxxx;\n*  div.addEventListener('click', handler);\n*  return () =\u003e {\n*    div.removeEventListener('click', handler);\n*  }\n*}, []);\n*\n*// BAD\n*const divRef = useRef(null);\n*useEffect(() =\u003e {\n*  if (!divRef.current) {\n*    return;\n*  }\n*  const someAsyncMethod = async() =\u003e {\n*    await a_few_moments_later();\n*    // 这里 divRef.current 可能为 null\n*    doSomethingWith(divRef.current);\n*  }\n*  someAsyncMethod();\n*\n*  const handler = () =\u003e xxxx;\n*  divRef.current.addEventListener('click', handler);\n*  return () =\u003e {\n*    // React v17 开始，这里的 divRef 可能是 null\n*    // useEffect 返回的 callback 函数也是异步执行的\n*    // 如果使用 TypeScript，下面的代码可能自动补全 optionalChain\n*    // 此时如果 handler 中有 binding 外部闭包变量，将导致内存泄漏\n*    // 造成 detached DOM 常驻内存中\n*    divRef.current.removeEventListener('click', handler);\n*  }\n*}, []);\n*2.4.5 [强制] 只在 React 函数或自定义 hooks 中调用 hooks\n解释：\n*不要在一般函数中调用 Hooks。这条规则保证了组件的所有状态逻辑都能在组件代码中清晰可见\n*2.5 组件声明\n*2.5.1 [强制] 优先使用函数组件，当需要使用 componentDidCatch 时，可考虑使用 ClassComponent\n解释：\n*函数组件在React中有着特殊的地位，在将来也有可能得到更多的内部优化\n示例：\n*// Good\n*const NextNumber = ({value}) =\u003e \u003cspan\u003e{value + 1}\u003c/span\u003e;\n*\n*// Bad\n*class NextNumber {\n*  render() {\n*    const { value } = this.props;\n*    return \u003cspan\u003e{value + 1}\u003c/span\u003e;\n*  }\n*}\n*2.5.2 [建议] 使用箭头函数声明函数组件\n解释：\n*箭头函数具备更简洁的语法（无需 function 关键字），且可以在仅有一个语句时省去 return 造成的额外缩进\n示例：\n*// GOOD\n*export const MyComponent = props =\u003e { /* ... */ }\n*\n*// BAD\n*export function MyComponent(props) {/* ... */}\n*2.6 组件实现规范\n*2.6.1 [建议] 除顶层或路由级组件以外，所有组件均在概念上实现为纯组件\n解释：\n*本条规则并非要求组件一定继承自 PureComponent，“概念上的纯组件”的意思为一个组件在 props 和 state 没有变化（shallowEqual）的情况下，渲染的结果应保持一致，与纯函数类似。\n*\n*非纯组件具备向上的“传染性”，即一个包含非纯组件的组件也必须是非纯组件，依次沿组件树结构向上。由于非纯组件无法通过 shouldComponentUpdate 优化渲染性能且具备传染性，因此要避免在非顶层或路由组件中使用。\n*\n*如果需要在组件树的某个节点使用随机数、日期等非纯的数据，应当由顶层组件生成这个值并通过 props 传递下来。对于使用 Redux 等应用状态管理的系统，可以在应用状态中存放相关值（如 Redux使用 Action Creator 生成这些值并通过 Action 和 reducer 更新到 store 中）\n示例：\n*// GOOD\n*const RandomNumber = ({ number }) =\u003e (\u003cspan\u003e{number}\u003c/span\u003e);\n*// 顶层组件\n*class SomeContainer extends Component {\n*  state = {\n*    random: Math.random(),\n*  }\n*\n*  render () {\n*    return (\n*      \u003cRandomNumber number={this.state.random}/\u003e\n*    );\n*  }\n*}\n*\n*// BAD\n*const RandomNumber = () =\u003e \u003cspan\u003e{Math.random()}\u003c/span\u003e;\n*const Clock = () =\u003e \u003cspan\u003e{Date.time()}\u003c/span\u003e;\n*2.6.2 [建议] 为非继承自 PureComponent 的纯组件实现 shouldComponentUpdate 方法\n解释：\n*如果一个组件比较前后两次渲染状态的计算量比 render 小且不会因为父组件状态变更而频繁变更的话，shouldComponentUpdate 通常能减少不必要的重绘，带来较好的性能优化。但这不是绝对的。\n*如果滥用 PureComponent 和 shouldComponentUpdate 在很多情况下对性能也有负面影响，相关材料:\n*https://twitter.com/dan_abramov/status/759383530120110080\n*https://twitter.com/dan_abramov/status/820668074223353858\n*https://twitter.com/dan_abramov/status/1006871955126063105\n*2.6.3 [建议] 为函数组件添加 PureComponent 能力\n解释：\n*函数组件并非一定是纯组件，因此其 shouldComponentUpdate 的实现为 return true;，这可能导致额外的无意义渲染，因此推荐使用 React.memo 对函数组件进行优化\n示例：\n*// GOOD\n*import React, { memo } from 'react';  \n*function PercentageStat({ score = 0, total = Math.max(1, score) }) {  \n*  return (\n*    \u003cspan\u003e{ Math.round(score / total * 100) }%\u003c/span\u003e   \n*  );\n*}  \n*// 使用 `React.memo()` 包裹组件\n*export default memo(PercentageStat);\n*2.6.4 [建议] 优先使用 Functional Component + hooks\n解释：\n*在 React 版本允许的情况下，优先使用 hooks，代码量更少，逻辑更清晰；但不推荐将旧组件改为 hooks 实现\n*3 文件组织\n*3.1 组件目录\n*3.1.1 [强制] UI 组件以单个文件夹目录维护，有且只有一个 index 文件作为导出，业务组件（UI 组件 + 业务逻辑）用单文件维护\n解释：\n*UI 组件一般会有 css、icon、子 UI 文件，作为一个黑盒输出给业务使用，所以以目录方式维护结构更清晰。\n*业务组件使用单个文件维护可以做到强制抽离 UI 部分和逻辑部分的目的。\n示例：\n*// GOOD\n*/MyAwesomeComponent\n*/MyAwesomeComponent/index.(j|t)sx?\n*/MyAwesomeComponent/style.(le|s)?css\n*/MyAwesomeComponent/SomeOtherComponent.(j|t)sx?\n*\n*// BAD\n*/MyAwesomeComponent\n*/MyAwesomeComponent/index.(j|t)sx?\n*/MyAwesomeComponent/style.(le|s)?css\n*/MyAwesomeComponent/SomeOtherComponent.(j|t)sx?\n*3.1.2 [强制] 同一目录下不得拥有同名的 .js、. jsx 和 .tsx 文件\n解释：\n*在使用模块导入时，倾向于不添加后缀，如果存在同名但不同后缀的文件，构建工具将无法决定哪一个是需要引入的模块\n*示例：\n*\n*3.1.3 [强制] 组件文件使用一致的 .jsx、.tsx后缀\n*4 特定第三方库、特定场景规范\n*4.1 react-redux 相关\n*4.1.1 [建议] connect 参数使用建议命名\n解释：\n*connect 函数有四个可选参数：\n*mapStateToProps\n*mapDispatchToProps\n*mergeProps\n*options\n*这条规则强制所提供的参数遵循上述命名习惯\n示例：\n*// GOOD\n*connect(mapStateToProps, mapDispatchToProps, mergeProps)(TodoApp)\n*connect()(TodoApp)\n*\n*// BAD\n*connect(mapStateToProps, actionCreators)(TodoApp)\n*4.1.2 [建议] mapDispatchToProps 参数使用建议命名\n解释：\n*mapDispatchToProps 函数有两个可选参数：\n*dispatch\n*ownProps\n*提供的参数应遵循上述命名习惯\n示例：\n*// GOOD\n*const mapDispatchToProps = () =\u003e {...};\n*const mapDispatchToProps = (dispatch, ownProps) =\u003e {...};\n*const mapDispatchToProps = (dispatch, { prop1, prop2 }) =\u003e {...};\n*const mapDispatchToProps = (dispatch) =\u003e {...};\n*connect(mapStateToProps, mapDispatchToProps)(App);\n*\n*// BAD\n*const mapDispatchToProps = (anyOtherName) =\u003e {};\n*connect((state) =\u003e state, (anyOtherName) =\u003e {})(App);\n*4.1.3 [建议] mapStateToProps 参数使用建议命名\n解释：\n*mapStateToProps 函数有两个可选参数：\n*dispatch\n*ownProps\n*提供的参数应遵循上述命名习惯\n示例：\n*// GOOD\n*const mapStateToProps = (state, ownProps) =\u003e {...};\n*const mapStateToProps = state =\u003e {...};\n*const mapStateToProps = ({ isActive }) =\u003e {...};\n*connect(state =\u003e state, null)(App);\n*\n*// BAD\n*const mapStateToProps = (anyOtherName) =\u003e {...};\n*connect(function(anyOtherName) {...}, null)(App);\n*4.1.4 [建议] 禁止将整个 state 对象绑定到组件\n解释：\n*将整个 state 传入组件是错误的实践，这将导致非必要的渲染，应该传入组件所需的具体属性\n示例：\n*// GOOD\n*const mapStateToProps = () =\u003e {...};\n*const mapStateToProps = state =\u003e { isActive: state.isActive };\n*const mapStateToProps = ({ isActive }) =\u003e {...};\n*connect(state =\u003e ({ isActive: state.isActive }), null)(App);\n*\n*// BAD\n*const mapStateToProps = state =\u003e state;\n*const mapStateToProps = state =\u003e {\n*  return { state: state };\n*}\n*const mapStateToProps = state =\u003e ({ ...state });\n*connect(state =\u003e state, null)(App);\n*4.1.5 [建议] 如果使用的 React 版本支持 hook，在 FunctionComponent 中，使用 react-redux@^7.1.0 的 hook 来节省范式代码\n解释：\n*可以少打很多字\n示例：\n*// GOOD\n*import { useDispatch, useSelector } from 'react-redux';\n*\n*function MyInternalFunctionComponent(props) {\n*  const stateInStore = useSelector(state =\u003e pick(state, fields));\n*  const dispatch = useDispatch();\n*  const onChange = useCallback(v =\u003e {\n*    dispatch(createChangeAction(props.doSomething(v)));\n*  }, [dispatch]);\n*  return /* My JSX */\n*}\n*\n*// BAD\n*import { connect } from 'react-redux';\n*\n*function MyInternalFunctionComponent(props) {\n*  return /* My JSX */\n*}\n*const mapStateToProps = state =\u003e pick(state, fields);\n*const mapDispatchToProps = (dispatch, ownProps) =\u003e ({\n*  onChange(v) {\n*    dispatch(createChangeAction(ownProps.doSomething(v)));\n*  }\n*});\n*export const MyFunctionComponent = connect(\n*  mapStateToProps,\n*  mapDispatchToProps,\n*)(MyInternalFunctionComponent);\n*4.2 styled-components\n*4.2.1 [强制] 禁止在 Array map 中使用非 static styled-components，应使用 attrs\n解释：\n*styled-components 会为每 props 映射出来的 style 新增一种 class，用 \u003cstyle\u003e 标签插入到 html 文档中。当 props 在 map 循环中有很多影响 style 的值时，会导致性能问题\n*这种情况应使用 attrs 内联或者 JSX 中插入 style 属性\n示例：\n*// GOOD\n*const Row = styled.li.attr(props =\u003e ({ style: { height: props.imgHeight } }))`\n*  // some style\n*`;\n*// snip:start\n*{data.map(datum =\u003e (\n*  \u003cRow key={datum.src} height={datum.imgHeight}\u003e\u003cimg src={datum.src} /\u003e\u003c/Row\u003e\n*))}\n*// snip:end\n*\n*// BAD\n*const Row = styled.li`\n*  // some style\n*  height: ${props =\u003e props.height}px; // 如果有 100 个 height，会生成 100 个 class\n*`;\n*// snip:start\n*{data.map(datum =\u003e (\n*  \u003cRow key={datum.src} height={datum.imgHeight}\u003e\u003cimg src={datum.src} /\u003e\u003c/Row\u003e\n*))}\n*// snip:end\n*4.2.2 [强制] 禁止在 render、React.FC 中使用 styled 声明新的 component\n解释：\n*styled 会返回一个新的 ComponentType 导致重绘\n*即使使用 useMemo，或者任何自定义的缓存方式，也无法保障 SSR 时候，生成的 styled-components 的 componentId 一致（这会导致 html 输出的 class 和 CSR 恢复时 class 名不一致，造成样式丢失）\n示例：\n*// GOOD\n*const Div = styled.div`\n*  // some css`;\n*const SomeComponent = () =\u003e {\n*  return \u003cDiv {...someProps} /\u003e;\n*}\n*\n*// BAD\n*const SomeComponent = () =\u003e {\n*  const Div = React.useMemo(() =\u003e {\n*    return styled.div`\n*      // some css\n*    `;\n*  }, []);\n*  return \u003cDiv {...someProps} /\u003e;\n*}\n*4.3 antd\n*4.3.1 [强制] Form 组件禁止使用 getFieldProps getFieldDecorator 声明了 ReactElement 但不渲染\n解释：\n*rc-form 字段回收时机问题\n*rc-form 回收字段的机制是在 getFieldProps 或者 getFieldDecorator 时注入 ref 回调函数，如果组件 unmount，会触发 ref(null)，此时会把相应字段删除。如果代码用 getFieldProps 或者 getFieldDecorator 生成了 field，但没有输出在 ReactDOMTree 上，会导致不会执行 ref 回调。这时会发现字段无法正常删除\n示例：\n*// GOOD\n*const { type } = this.props;\n*return type === 'a'\n*  ? getFieldDecorator('fieldA')(\u003cInput /\u003e)\n*  : getFieldDecorator('fieldB')(\u003cCheckbox /\u003e);\n*\n*// BAD\n*const { type } = this.props;\n*const elem1 = getFieldDecorator('fieldA')(\u003cInput /\u003e);\n*const elem2 = getFieldDecorator('fieldB')(\u003cCheckbox /\u003e);\n*return type === 'a' ? elem1 : elem2;\n*4.3.2 [强制] Form 组件禁止 getFieldProps getFieldDecorator 使用重复 id\n解释：\n*rc-form 字段回收时机问题\n*跟上一条原因差不多，当其中一个声明的组件执行 unmount，对应的字段删除了。但是，因为 unmount 声明周期是在所有 component render 之后，这是后页面显示的内容还在，但是值其实已经被删除了，导致视图和 state 不一致，增加排查问题难度\n示例：\n*// GOOD\n*// 如果需要表单字段联动，可以在 onchange 时触发另一个字段变更\n*\u003c\u003e\n*  {getFieldDecorator('fieldA-1', {\n*    onChange(v) {\n*      form.setFieldsValue({ 'fieldA-1': v, 'fieldA-2': v });\n*    }\n*  })(\u003cInput /\u003e)}\n*  {getFieldDecorator('fieldA-2', {\n*    onChange(v) {\n*      form.setFieldsValue({ 'fieldA-1': v, 'fieldA-2': v });\n*    }\n*  })(\u003cInput /\u003e)}\n*\u003c/\u003e\n*\n*// BAD\n*\u003c\u003e\n*  {getFieldDecorator('fieldA-1')(\u003cInput1 /\u003e)}  // 相同的 field 会导致下面 Input2 因为 someCondition 被删除时，fieldA-1 丢失，而页面依然显示\n*  {someCondition \u0026\u0026 getFieldDecorator('fieldA-1')(\u003cInput2 /\u003e)}\n*\u003c/\u003e\n*4.3.3 [强制] Form 组件禁止切换组件 type 渲染同一个 id\n示例：\n*// GOOD\n*\u003c\u003e\n*  {type === 'Input' \u0026\u0026 getFieldDecorator('fieldInput')(\u003cInput /\u003e)}\n*  {type === 'Select' \u0026\u0026 getFieldDecorator('fieldSelect')(\u003cSelect /\u003e)}  \n*\u003c/\u003e\n*\n*// BAD\n*\u003c\u003e\n*  {type === 'Input' \u0026\u0026 getFieldDecorator('field')(\u003cInput /\u003e)}\n*  {type === 'Select' \u0026\u0026 getFieldDecorator('field')(\u003cSelect /\u003e)}  \n*\u003c/\u003e\n*4.4 TypeScript\n*4.4.1 [建议] 尽量提供完整的泛型\n解释:\n*React 的 api 大量依赖泛型，提供尽量完整的泛型有助于代码提示及错误及早发现\n示例：\n*// GOOD\n*interface Props {\n*  x: number\n*}\n*function A(props: Props) {}\n*class B extends Component\u003cProps, State\u003e {}\n*\n*// BAD\n*function A(props: any) {}\n*class B extends Component\u003cany, any\u003e {}\n*5 HTML 相关\n*5.1 a11y 相关\n*5.1.1 [建议] 如果文本中有 emoji 字符，使用 \u003cspan\u003e 标签包裹它，并设置role=\"img\" aria-label=\"emojiname\"\n解释：\n*无障碍阅读\n示例：\n*// GOOD\n*\u003cspan role=\"img\" aria-label=\"Snowman\"\u003e\u0026#9731;\u003c/span\u003e\n*\u003cspan role=\"img\" aria-label=\"Panda\"\u003e🐼\u003c/span\u003e\n*\u003cspan role=\"img\" aria-labelledby=\"panda1\"\u003e🐼\u003c/span\u003e\n*\n*// BAD\n*\u003cspan\u003e🐼\u003c/span\u003e\n*\u003ci role=\"img\" aria-label=\"Panda\"\u003e🐼\u003c/i\u003e\n*5.1.2 [建议] input[type=image]、img、area、object 标签添加必要的内容说明属性（alt、title、aria-label）\n解释：\n*无障碍阅读\n示例：\n*// GOOD\n*\u003cimg src=\"foo\" alt=\"Foo eating a sandwich.\" /\u003e\n*\u003cobject aria-label=\"foo\" /\u003e\n*\u003cobject title=\"An object\" /\u003e\n*\u003carea aria-label=\"foo\" /\u003e\n*\u003carea aria-labelledby=\"id1\" /\u003e\n*\u003carea alt=\"This is descriptive!\" /\u003e\n*\u003cinput type=\"image\" alt=\"This is descriptive!\" /\u003e\n*\u003cinput type=\"image\" aria-label=\"foo\" /\u003e\n*\u003cinput type=\"image\" aria-labelledby=\"id1\" /\u003e\n*\n*// BAD\n*\u003cimg src=\"foo\" /\u003e\n*\u003cimg {...props} /\u003e\n*\u003cobject {...props} /\u003e\n*\u003carea {...props} /\u003e\n*\u003cinput type=\"image\" {...props} /\u003e\n*5.1.3 [建议] \u003ca\u003e 标签必须有内容\n示例：\n*// GOOD\n*\u003ca\u003eAnchor Content!\u003c/a\u003e\n*\u003ca\u003e\u003cTextWrapper /\u003e\u003c/a\u003e\n*\u003ca dangerouslySetInnerHTML={{ __html: 'foo' }} /\u003e\n*\n*// BAD\n*\u003ca /\u003e\u003ca\u003e\n*\u003cTextWrapper aria-hidden /\u003e\u003c/a\u003e\n*5.1.4 [建议] \u003ca\u003e 标签如果没有有意义的 href，建议换成 button\n解释：\n*语义化\n示例：\n*// GOOD\n*\u003cbutton onClick={foo} \u003ePerform action\u003c/button\u003e\n*\u003cbutton href=\"#\" onClick={foo} \u003ePerform action\u003c/button\u003e\n*\u003cbutton onClick={foo} \u003ePerform action\u003c/button\u003e\n*\n*// BAD\n*\u003ca href=\"javascript:void(0)\" onClick={foo} \u003ePerform action\u003c/a\u003e\n*\u003ca href=\"#\" onClick={foo} \u003ePerform action\u003c/a\u003e\n*\u003ca onClick={foo} \u003ePerform action\u003c/a\u003e\n\n*关于 Hooks\nHooks 目前还没有形成很好的范式代码和实践，这里先暂时放一下比较基础的闭坑资料：\n*useCallback hell问题总结 \n*致命的 useCallback/useMemo（翻译） \n \n\n*\n\n\n\n"}
    const react2 = {
      "text":"React 代码规范\n*背景\n整理 React 项目的代码规范\n撰写人：        \n*1 命名规范\n*1.1 组件命名规范\n*1.1.1 [建议] 普通组件命名\n解释：\n*使用首字母大写驼峰（Pascal）形式命名，无论是否使用了 JSX 语法，文件后缀为(j|t)sx\n*通过文件后缀，快速识别代码性质，命名一致则可以让各种工具快速定位文件 (比如 grep、ag)\n示例：\n*// GOOD\n*// MyAwesomeComponent/index.jsx\n*class MyAwesomeComponent extends React.Component {\n*  render() { \n*    return null; \n*  }  // 虽然没有 jsx 语法，但它还是个 React 组件，用 jsx 作为后缀标记文件性质\n*}\n*\n*// BAD\n*// MyAwesomeComponent/index.js\n*class MyAwesomeComponent extends React.Component {\n*  render() { \n*    return null;\n*  }\n*}\n*1.1.2 [强制] 组件名与文件名、目录名保持相同\n解释：\n*文件名、文件夹名应当能体现出组件的功能，以便通过观察文件名即确定使用哪一个组件\n*方便 非 IDE 工具检索文件\n示例：\n*// 假设有个组件叫 InputNumber\n*// GOOD\n*InputNumber/index.tsx\n*InputNumber.tsx\n*\n*// BAD\n*Input/index.tsx\n*Number/index.tsx\n*Input.tsx\n*Number.tsx\n*input-number/index.tsx\n*input-number.tsx\n*1.2 Hooks 命名规范\n*1.2.1 [强制] Hook 组件文件名应以 useXXXX 命名，use 开头驼峰形式\n解释：\n*与 React 对于 Hook 的命名一致，增加识别度\n示例：\n*// GOOD\n*useMyAwesome.js\n*\n*// BAD\n*myAwesomeHook.js\n*1.2.2 [建议] useRef 返回的变量名以 Ref 结尾\n解释：\n*在使用 Hook 时，变量名称极其容易冲突。为了提高变量名的识别度，编码时保持对 ref 使用时的敏感度，用 suffix 来标记变量名\n示例：\n*// GOOD\n*const divRef = useRef(null);\n*\n*// BAD\n*const div = useRef(null);\n*1.3 props 命名规则\n*1.3.1 [建议] 使用 onXxx 形式作为 props 中用于回调的属性名称\n解释：\n*使用统一的命名规则用以区分props中回调和非回调部分的属性，在JSX上可以清晰地看到一个组件向上和向下的逻辑交互\n*对于不用于回调的函数类型的属性，使用动词作为属性名称\n示例：\n*// onClick作为回调以on开头，renderText非回调函数则使用动词\n*const Label = ({onClick, renderText}) =\u003e \u003cspan onClick={onClick}\u003e{renderText()}\u003c/span\u003e;\n*1.3.2 [建议] 如果一个函数是一个自渲染函数（返回的是 ReactNode 类型），使用 renderXXX 命名\n解释：\n*与其他函数区分开，通过 prefix 快速识别出函数是干什么的\n示例：\n*// GOOD\n*class MyComponent extends React.Component {\n*  render() {\n*    const { mode } = this.props;\n*    return (\n*      \u003c\u003e\n*        \u003cHeader /\u003e\n*        \u003cContent\u003e\n*        {mode === 'HORIZONTAL'\n*          ? this.renderHorizontalContent()\n*          : this.renderVerticalContent()\n*        }\n*        \u003c/Content\u003e\n*        \u003cFooter /\u003e\n*      \u003c/\u003e\n*    );\n*  }\n*  renderHorizontalContent = () =\u003e {}\n*  renderVerticalContent = () =\u003e {}\n*}\n*\n*// BAD\n*class MyComponent extends React.Component {\n*  render() {\n*    const { mode } = this.props;\n*    return (\n*      \u003c\u003e\n*        \u003cHeader /\u003e\n*        \u003cContent\u003e\n*        {mode === 'HORIZONTAL'\n*            ? this.horizontalContent()\n*            : this.verticalContent()\n*        }\n*        \u003c/Content\u003e\n*        \u003cFooter /\u003e\n*      \u003c/\u003e\n*    );\n*  }\n*  horizontalContent = () =\u003e {}\n*  verticalContent = () =\u003e {}\n*}\n*1.4 组件方法命名规则\n*1.4.1 [建议] 作为组件方法的事件处理函数以具备业务含义的词作为名称，使用 handleXxx 形式命名\n示例：\n*// Good\n*class Form {\n*  handleCollectAndSubmitData = () =\u003e {\n*    const { onSubmit } = this.props;\n*    const { name, age } = this.state;\n*    const data = {\n*      name,\n*      age,\n*    };\n*    onSubmit(data);\n*  };\n*  handleAyncName = () =\u003e {\n*    // ...\n*  };\n*  handleSyncAge = () =\u003e {\n*    // ...\n*  };\n*  render() {\n*    return (\n*      \u003cdiv\u003e\n*        \u003clabel\u003e\n*          姓名：\n*          \u003cinput type=\"text\" onChange={this.handleAsyncName} /\u003e\n*        \u003c/label\u003e\n*        \u003clabel\u003e\n*          年龄：\n*          \u003cinput type=\"number\" onChange={this.handleSyncAge} /\u003e\n*        \u003c/label\u003e\n*        \u003cbutton type=\"button\" onClick={this.handleCollectAndSubmitData}\u003e\n*          提交\n*        \u003c/button\u003e\n*      \u003c/div\u003e\n*    );\n*  }\n*}\n*2 组件实现规范\n*2.1 React API、生命周期相关规范\n*2.1.1 [强制] 不要使用 UNSAFE_componentWillMount、UNSAFE_componentWillReceiveProps、UNSAFE_componentWillUpdate 的生命周期\n解释：\n*UNSAFE_xxxx 即将在 17.x 被废弃，这几个状态属于视图和当前 state 不匹配的状态（比如 componentWillUpdate，此时页面显示和当前 state 不一致），在这几个生命周期取值、处理数据，较易发生难以察觉的问题\n示例：\n*// GOOD\n*componentDidMount() {\n*  this.componentDidUpdate();\n*}\n*componentDidUpdate(prevProps) {\n*  const { id } = this.props;\n*  if (!prevProps || id !== prevProps.id) {\n*    this.fetchData(id);\n*  }\n*}\n*\n*// BAD\n*componentWillMount() {\n*  this.componentWillUpdate();\n*}\n*componentWillUpdate(nextProps) {\n*  const { id } = this.props;\n*  if (!nextProps || nextProps.id !== id) {\n*    this.fetchData(nextProps.id);\n*  }\n*}\n*2.1.2 [建议] 除了 componentDidMount componentWillUnmount componentDidUpdate 三个生命周期，禁止出现 window、document、navigator 这类浏览器的全局变量\n解释：\n*componentDidMount componentWillUnmount componentDidUpdate 不会在 SSR 时执行\n*2.1.3 [强制] 禁止在 componentWillUnmount 时 setState\n*2.1.4 [强制] 禁止在 componentDidMount componentDidUpdate 时没有判断条件地调用 setState\n解释：\n*componentDidUpdate 会造成死循环\n*2.1.5 [强制] 禁止直接修改 this.state\n示例：\n*// GOOD\n*this.setState({ foo: 'foo', baz: 'baz' });\n*this.setState(s =\u003e ({ ...s, foo: 'foo', baz: 'baz' }));\n*\n*// BAD\n*this.state.foo = 'foo';\n*Object.assign(this.state, { baz: 'baz' });\n*this.setState({});\n*2.1.6 [强制] 禁止在 getDerivedStateFromProps 中，没有判断条件地返回特定 state，且至少返回 null\n解释：\n*避免滥用 getDerivedStateFromProps。一般此 api 是用来代替 UNSAFE_componentWillReceiveProps 这个生命周期，处理非完全受控组件内部状态和外部状态同步\n示例：\n*// GOOD\n*static getDerivedStateFromProps(props, state) {\n*  if (props.defaultValue === undefined \u0026\u0026 props.value) {\n*    // 假设组件如果有 defaultValue，说明是个非受控组件，渲染以 state 为准\n*    // 如果有 value，说明组件是个受控组件，渲染以 props 为准\n*    return {\n*      ...state,\n*      value: props.value,\n*    }\n*  }\n*  return null;\n*}\n*\n*// BAD\n*static getDerivedStateFromProps(props, state) {\n*  return {\n*    id: props.id,\n*    xxx: props.xxx,\n*  };\n*}\n*// 部分任何情况的映射，不如以 getter 形式来获取\n*get computedProps() {\n*  const { id, xxx } = this.props;\n*  return { id, xxx };\n*}\n*2.1.7 [建议] 禁止 this.setState 中使用 this.state\n解释：\n*this.state 不是同步更新的，可能导致非预期结果\n示例：\n*function increment() {\n*  const { value } = this.state;\n*  this.setState({ value: value + 1 });\n*}\n*// 如果两次调用同时发生，结果相当于\n*setState({ value: 1 + 1 });\n*setState({ value: 1 + 1 });\n*// 可以使用回调函数避免此问题\n*function increment() {\n*  this.setState(prevState =\u003e ({ value: prevState.value + 1 }));\n*}\n*// 这样快速调用时，结果相当于\n*setState({ value: 1 + 1 });\n*setState({ value: 2 + 1 });\n*\n*// GOOD\n*this.setState(prevState =\u003e ({ value: prevState.value + 1 }));\n*\n*// BAD\n*this.setState({ value: this.state.value + 1 });\n*2.1.8 [强制] 禁止使用已废弃的方法或属性\n解释：\n*已废弃或不推荐使用的 API 如下:\n*componentWillMount、componentWillUpdate、componentWillReceiveProps、replaceState、getInitialState、isMounted、createClass、mixin、createFactory、ReactDOM.findDOMNode\n*V17 即将废弃的 API 如下:\n*UNSAFE_componentWillMount、UNSAFE_componentWillUpdate、UNSAFE_componentWillReceiveProps\n*2.1.9 [强制] 禁止在 PureComponent 中设置 shouldComponentUpdate 方法\n解释：\n*如果一定要设置此方法，考虑使用 Component\n*React的相关Issue\n*在React的实现中，PureComponent 并不直接实现 shouldComponentUpdate，而是添加一个 isReactPureComponent 的标记，由 CompositeComponent 通过识别这个标记实现相关的逻辑。因此在 PureComponent 上自定义 shouldComponentUpdate 并无法享受 super.shouldComponentUpdate 的逻辑复用，也会使得这个继承关系失去意义\n*2.1.10 [强制] 禁止使用ReactDOM.render方法的返回值\n示例：\n*// GOOD\n*ReactDOM.render(\u003cApp /\u003e, document.body, doSomethingWithInst);\n*\n*// BAD\n*const inst = ReactDOM.render(\u003cApp /\u003e, document.body);\n*doSomethingWithInst(inst);\n*2.1.11 [强制] 禁止使用字符串ref\n解释：\n*官方已废弃此用法 详细\n示例：\n*// GOOD\n*this.myRef = React.createRef();\n*\u003cinput ref={this.myRef} /\u003e\n*\n*const myRef = React.useRef(null);\n*\u003cinput ref={myRef} /\u003e\n*\n*this.getRef = (dom) =\u003e this.myInput = dom;\n*\u003cinput ref={this.getRef} /\u003e\n*\n*// BAD\n*\u003cinput ref=\"inputRef\" /\u003e\n*2.1.12 [建议] 生命周期的书写顺序\n*static displayName\n*static propTypes\n*static contextTypes\n*static defaultProps\n*其它静态的属性\n*state\n*constructor\n*public 方法（非生命周期的方法，组件向外部暴露的方法，外部可以通过 ref 调用的方法） \n*getChildContext\n*componentWillMount (react 16 后不应该使用)\n*componentDidMount\n*componentWillReceiveProps (react 16 后不应该使用)\n*shouldComponentUpdate\n*componentWillUpdate (react 16 后不应该使用)\n*componentDidUpdate\n*componentWillUnmount\n*事件处理函数，如： handleClickSubmit() or handleChangeDescription()\n*自定义方法，不属于17、19、20的方法或无法归类的方法，如：fetchData()\n*render 方法的 getter，如： getSelectReason() or getFooterContent()\n*某些抽取的部分 render 方法，如： renderNavigation() or renderProfilePicture()\n*render\n解释：\n*按照 react 生命周期的执行顺序排序，render 函数放在最下面容易查找阅读\n示例：\n*class A {\n*  componentDidMount() {}\n*  xxx() {}\n*  render() {}\n*}\n*2.1.13 [建议]TSX中，组件方法需要声明private、protected，public 无需书写，遵循TS规范\n示例：\n*// Good\n*class B {\n*  componentDidMount() {}\n*  private handleButtonClick() {}\n*  render() {}\n*}\n*2.1.14 [建议] 每个组件都要写 propTypes，TSX 中可以省略\n*2.2 JSX 相关规范\n*2.2.1 [强制] 没有子节点的非DOM组件使用自闭合语法\n解释：\n*对于DOM节点，按照HTML编码规范相关规则进行闭合，其中void element使用自闭合语法。\n示例：\n*// Good\n*\u003cFoo /\u003e\n*\n*// Bad\n*\u003cFoo\u003e\u003c/Foo\u003e\n*2.2.2 [强制] 保持起始和结束标签在同一层缩进\n解释：\n*对于标签前面有其它语句（如return的情况，使用括号进行换行和缩进）。\n示例：\n*// Good\n*class Message {\n*  render() {\n*    return (\n*      \u003cdiv\u003e\n*        \u003cspan\u003eHello World\u003c/span\u003e\n*      \u003c/div\u003e\n*    );\n*  }\n*}\n*\n*// Bad\n*class Message {\n*  render() {\n*    return \u003cdiv\u003e\n*        \u003cspan\u003eHello World\u003c/span\u003e\n*      \u003c/div\u003e\n*    ;\n*  }\n*}\n对于直接return的函数组件，可以直接使用括号而省去大括号和return关键字：\n*const Message = () =\u003e (\n*  \u003cdiv\u003e\n*    \u003cspan\u003eHello World\u003c/span\u003e\n*  \u003c/div\u003e\n*);\n*2.2.3 [强制] JSX中的props缩进为2个空格，且props与value之间的等号前后禁止有空格\n解释：\n*提高代码可读性。\n示例：\n*// Good\n*// 2 spaces indentation, no space around prop and value\n*\u003cHello\n*  firstName={firstName}\n*  lastName={lastName}\n*/\u003e\n*\n*// Bad\n*// no indentation\n*\u003cHello firstName=\"John\"\n*/\u003e\n*// 1 tab indentation\n*\u003cHello\n*  firstName=\"John\"\n*/\u003e\n*// has space around prop and value\n*\u003cHello firstName = {firstName} /\u003e;\n*\u003cHello firstName ={firstName} /\u003e;\n*\u003cHello firstName= {firstName} /\u003e;\n*2.2.4 [强制] 自闭合标签的/\u003e前添加一个空格\n示例：\n*// Good\n*\u003cFoo bar=\"bar\" /\u003e\n*\n*// Bad\n*\u003cFoo bar=\"bar\"/\u003e\n*\u003cFoo bar=\"bar\"  /\u003e\n*2.2.5 [强制] 以字符串字面量作为值的属性使用双引号\"，在其它类型表达式中的字符串使用单引号'\n解释：\n*对齐 html 规范\n示例：\n*// GOOD\n*\u003cComponent prop=\"value\" /\u003e\u003cFoo style={{ width: '20px' }} /\u003e\n*\n*// BAD\n*\u003cComponent prop='value' /\u003e\u003cFoo style={{ width: \"20px\" }} /\u003e\n*2.2.6 [强制] 对于值为true的属性，省去值部分\n解释：\n*符合html的规范，如: \u003cradio checked\u003e选中\u003c/radio\u003e\n示例：\n*// Good\n*\u003cFoo visible /\u003e\n*\n*// Bad\n*\u003cFoo visible={true} /\u003e\n*2.2.7 [强制] 避免在JSX的属性值中直接使用对象或函数表达式\n解释：\n*PureComponent使用shallowEqual对props和state进行比较来决定是否需要渲染，而在JSX的属性值中使用对象、函数表达式会造成每一次的对象引用不同，从而shallowEqual会返回false，导致不必要的渲染。\n示例：\n*// Good\n*class WarnButton {\n*  alertMessage = () =\u003e {\n*    const { message } = this.props;\n*    alert(message);\n*  };\n*\n*  render() {\n*    return (\n*      \u003cbutton type=\"button\" onClick={this.alertMessage}\u003e\n*        提示\n*      \u003c/button\u003e\n*    );\n*  }\n*}\n*\n*// Bad\n*class WarnButton {\n*  alertMessage(message) {\n*    alert(message);\n*  }\n*\n*  render() {\n*    const { message } = this.props;\n*    return (\n*      \u003cbutton\n*        type=\"button\"\n*        onClick={() =\u003e this.alertMessage(message)}\n*      \u003e\n*        提示\n*      \u003c/button\u003e\n*    );\n*  }\n*}\n*2.2.8 [强制] 使用button组件时，必须包含type属性\n解释：\n*在 \u003cform\u003e 标签中的 button 属性 type 默认值为 submit，会导致非预期的页面重载\n示例：\n*// GOOD\n*const Hello = \u003cbutton type=\"button\"\u003eHello\u003c/button\u003e;\n*const Hello = \u003cbutton type=\"submit\"\u003eHello\u003c/button\u003e;\n*const Hello = \u003cbutton type=\"reset\"\u003eHello\u003c/button\u003e;\n*\n*// BAD\n*const Hello = \u003cbutton\u003eHello\u003c/button\u003e;\n*const Hello = \u003cbutton type=\"foo\"\u003eHello\u003c/button\u003e;\n*2.2.9 [建议] \u003ca\u003e 标签如果指定 target=\"_blank\"，如果 href 是完整的 http 地址，必须同时指定 rel=\"noreferrer noopener\"\n解释：\n*防止追踪和通过 window.opener 访问、更改 location 详情\n*补：在未开启 Site Isolation 策略的游览器下，防止跳转的页面和当前页面共享一个渲染进程，让两个页面的渲染进程隔离 by  （2020/10/10 补充：实测 Chrome 67、FireFox 58 之后不存在此问题，Safari 待测试 TODO:   ）\n*复用渲染进程的优点：\n*共用渲染进程可复用共同资源、节省创建进程的开支（Chrome 67 之后已不再使用非同源站点的进程复用，故删掉此优点）\n示例：\n*// GOOD\n*\u003ca target=\"https://toutiao.com\" rel=\"noreferrer noopener\"\u003eJump to other site\u003c/a\u003e\n*\u003ca target=\"/contact\"\u003eJump to the same site\u003c/a\u003e\n*\n*// BAD\n*\u003ca target=\"https://toutiao.com\"\u003eJump to other site\u003c/a\u003e\n*2.2.10 [强制] 无状态函数组件（FC）中，不允许使用 this\n解释：\n*在改写class组件到function组件的过程中，可能导致遗漏\n示例：\n*// GOOD\n*function Foo(props) {\n*  const { bar } = props;\n*  return \u003cdiv\u003e{bar}\u003c/div\u003e;\n*}\n*\n*// BAD\n*function Foo(props) {\n*  const { bar } = this.props;\n*  return \u003cdiv\u003e{bar}\u003c/div\u003e;\n*}\n*2.2.11 [强制] 组件必须提供命名，高阶组件生成的组件需要指定 displayName，名字应含有高阶组件的功能和所包装组件的原名称\n解释：\n*如果不设置的话，debug tools中会显示Unknown，不方便调试，高阶组件由于是个函数，还是遵循首字母小写驼峰形式，比如：withXXXX、asXXXX。\n*PS：\n*如果一个匿名函数赋值给某个变量，那么这个匿名函数的 name 属性会自动变为该变量名字，所以无需担心赋值语句右值是匿名函数在 debug tools 显示 Unknown 的情况\n示例：\n*// GOOD\n*export default class Hello extends Component {}\n*\n*const Hello = () =\u003e {};\n*export default Hello;\n*\n*export const World = () =\u003e {};\n*\n*// 对于HoC\n*const asPureComponent = Component =\u003e {\n*  const componentName =\n*    Component.displayName || Component.name || \"UnknownComponent\";\n*  return class extends PureComponent {\n*    // 需要在这里声明displayName\n*    static displayName = `asPure(${componentName})`;\n*    render() {\n*      return \u003cComponent {...this.props} /\u003e;\n*    }\n*  };\n*};\n*\n*// BAD\n*export default class extends Component {}\n*export default () =\u003e {};\n*2.2.12 [强制] 禁止使用数组的 index 作为 key\n解释：\n*key 最好是唯一的，如果使用 index 作为 key，在排序或队首插入元素时，将导致不必要的计算，影响性能\n*什么情况下禁用\n*当确认列表数据顺序不变时，可禁用此条件\n示例：\n*// GOOD\n*things.map((thing) =\u003e (\n*  \u003cHello key={thing.id} /\u003e\n*));\n*\n*// BAD\n*things.map((thing，index) =\u003e (\n*  \u003cHello key={index} /\u003e\n*));\n*2.2.13 [强制] jsx、tsx 文件中必须引用 react\n解释：\n*使用 JSX 语法隐式地依赖当前环境下有 React，如果不显式引入，可能导致运行时 React 没有定义的报错。(比如，一开始是 webpack 设置 alias 全局引入 React，后来换成打到 bundle 里去，如果没有显式引入 React，就会报错)\n*什么时候可以禁用此条规则\n*在项目构建工具有使用 babel-plugin-react-require 插件时可以禁用\n示例：\n*// GOOD\n*import React from 'react';\n*export const StatelessComponent = () =\u003e \u003ch1\u003eHello world\u003c/h1\u003e;\n*\n*// BAD\n*export const StatelessComponent = () =\u003e \u003ch1\u003eHello world\u003c/h1\u003e;\n*// 上面的代码编译后会变为\n*React.createElement('h1', null, 'Hello world'); // 引入了 React 依赖\n*2.2.14 [强制] 禁止在使用了 dangerouslySetInnerHTML 的组件内添加 children\n解释：\n*使用 dangerouslySetInnerHTML 属性与 children 不可同时设置。\n示例：\n*// Good\n*\u003cdiv dangerouslySetInnerHTML={{ __html: 'HTML' }} /\u003e\n*\u003cHello dangerouslySetInnerHTML={{ __html: 'HTML' }} /\u003e\n*\u003cdiv\u003e\n*  Children\n*\u003c/div\u003e\n*\u003cHello\u003e\n*  Children\n*\u003c/Hello\u003e\n*React.createElement('div', { dangerouslySetInnerHTML: { __html: 'HTML' } });\n*React.createElement('Hello', { dangerouslySetInnerHTML: { __html: 'HTML' } });\n*React.createElement('div', {}, 'Children');\n*React.createElement('Hello', {}, 'Children');\n*\n*// Bad\n*\u003cdiv dangerouslySetInnerHTML={{ __html: 'HTML' }}\u003e\n*  Children\n*\u003c/div\u003e\n*\u003cHello dangerouslySetInnerHTML={{ __html: 'HTML' }}\u003e\n*  Children\n*\u003c/Hello\u003e\n*React.createElement('div', { dangerouslySetInnerHTML: { __html: 'HTML' } }, 'Children');\n*React.createElement('Hello', { dangerouslySetInnerHTML: { __html: 'HTML' } }, 'Children');\n*2.2.15 [强制] JSX.Element 如果有 style 属性，值必须是对象\n示例：\n*// GOOD\n*\u003cdiv style={{ color: 'red' }} /\u003e\n*\u003cHello style={{ color: 'red' }} /\u003econst styles = { color: 'red' };\n*\u003cdiv style={styles} /\u003e\n*React.createElement('div', { style: { color: 'red' } });\n*\n*// BAD\n*\u003cdiv style=\"color: 'red'\" /\u003e\n*\u003cHello style={true} /\u003e\n*const styles = true;\n*\u003cdiv style={styles} /\u003e\n*React.createElement('div', { style: 'color: red;' });\n*2.2.16 [建议] 对于多属性需要换行，从第一个属性开始，每个属性一行\n示例：\n*// Good\n*// 没有子节点\n*\u003cSomeComponent\n*  longProp={longProp}\n*  anotherLongProp={anotherLongProp}\n*/\u003e\n*\n*// 有子节点\n*\u003cSomeComponent\n*  longProp={longProp}\n*  anotherLongProp={anotherLongProp}\n*\u003e\n*  \u003cSomeChild /\u003e\n*  \u003cSomeChild /\u003e\n*\u003c/SomeComponent\u003e\n*\n*// Bad\n*// 2 props on the same line\n*\u003cSomeComponent\n*  longProp={longProp} anotherLongProp={anotherLongProp}\n*/\u003e\n*2.2.17 [建议] 将JSX的层级控制在3层以内\n解释：\n*JSX提供了基于组件的便携的复用形式，因此可以通过将结构中的一部分封装为一个函数组件来很好地拆分大型复杂的结构。层次过深的结构会带来过多缩进、可读性下降等缺点。如同控制函数内代码行数和分支层级一样，对JSX的层级进行控制可以有效提升代码的可维护性。\n示例：\n*// Good\n*const Header = ({title, subtitle}) =\u003e (\n*  \u003cheader\u003e\n*    \u003ch3\u003e{title}\u003c/h3\u003e\n*    \u003cspan\u003e{subtitle}\u003c/span\u003e\n*  \u003c/header\u003e\n*);\n*\n*const Content = ({content}) =\u003e \u003csection\u003e{content}\u003c/section\u003e;\n*\n*const Footer = ({author, postTime}) =\u003e (\n*  \u003cfooter\u003e\n*    \u003cspan\u003e{author}\u003c/span\u003e@\u003ctime\u003e{postTime}\u003c/time\u003e\n*  \u003c/footer\u003e\n*);\n*\n*const Item = item =\u003e (\n*  \u003cdiv\u003e\n*    \u003cHeader {...item} /\u003e\n*    \u003cContent {...item} /\u003e\n*    \u003cFooter {...item} /\u003e\n*  \u003c/div\u003e\n*);\n*\n*const List = ({ items }) =\u003e \u003cul\u003e{items.map(Item)}\u003c/ul\u003e;\n*\n*// Bad\n*const List = ({ items }) =\u003e (\n*  \u003cul\u003e\n*    {items.map(item =\u003e (\n*      \u003cli\u003e\n*        \u003cheader\u003e\n*          \u003ch3\u003e{item.title}\u003c/h3\u003e\n*          \u003cspan\u003e{item.subtitle}\u003c/span\u003e\n*        \u003c/header\u003e\n*        \u003csection\u003e{item.content}\u003c/section\u003e\n*        \u003cfooter\u003e\n*          \u003cspan\u003e{item.author}\u003c/span\u003e@\u003ctime\u003e{item.postTime}\u003c/time\u003e\n*        \u003c/footer\u003e\n*      \u003c/li\u003e\n*    ))}\n*  \u003c/ul\u003e\n*);\n*2.3 成员属性、成员函数规则\n*2.3.1 [强制] 禁止使用属性访问的方式使用其他组件的 propTypes\n解释：\n*这是为了在生产环境用 babel-plugin-transform-react-remove-prop-types 把 propTypes 相关代码去掉\n示例：\n*// GOOD\n*import SomeComponent, { propTypes as someComponentPropTypes } from './SomeComponent';\n*\n*// BAD\n*import SomeComponent from './SomeComponent';\n*SomeComponent.propTypes;\n*const { propTypes } = SomeComponent;\n*SomeComponent['propTypes'];\n*2.3.2 [强制] 为props中所有非必传属性设置默认值，对于propTypes设置为 isRequired 的属性不需要设置默认值，优先使用解构默认值的方式设置默认值\n解释：\n*既无default也没有设置required容易产生bug。\n*defaultProps 正在被官方弃用，所以建议优先使用解构默认值的方式来代替 defaultProps\n*https://github.com/facebook/react/pull/16210\n*https://medium.com/@matanbobi/react-defaultprops-is-dying-whos-the-contender-443c19d9e7f1\n示例：\n*// GOOD\n*class ClassComponent extends React.Component {\n*  static propTypes = {\n*    foo: PropTypes.string,\n*    bar: PropTypes.string.isRequired,\n*  };\n*  render() {\n*    const { foo = '', bar } = this.props; // 解构对象时赋予默认值\n*    // ...\n*  }\n*}\n*\n*FunctionComponent.propTypes = {\n*  foo: PropTypes.string,\n*  bar: PropTypes.string.isRequired,\n*};\n*function FunctionComponent(props) {\n*  const { foo: '', bar } = props;\n*  // ...\n*}\n*\n*// BAD\n*class ClassComponent extends React.Component {\n*  static propTypes = {\n*    // foo 已经定义在 defaultProps 中，不需要 isRequired\n*    foo: PropTypes.string.isRequired,\n*    // bar 没有定义在 defaultProps 中，需要加上 isRequired\n*    bar: PropTypes.string,\n*  };\n*  static defaultProps = {\n*    foo: \"\",\n*  };\n*}\n*\n*FunctionComponent.propTypes = {\n*  // foo 已经定义在 defaultProps 中，不需要 isRequired\n*  foo: PropTypes.string.isRequired,\n*  // bar 没有定义在 defaultProps 中，需要加上 isRequired\n*  bar: PropTypes.string,\n*};\n*FunctionComponent.defaultProps = {\n*  foo: \"\",\n*};\n*2.3.3 [强制] 使用 Class Property 语法声明 propTypes、contextTypes、defaultProps 和 state\n解释：\n*逻辑更清晰，一目了然\n示例：\n*// GOOD\n*class MyComponent extends React.Component {\n*  static propTypes = {};\n*  static defaultProps = {};\n*  state = {\n*    foo: this.props.foo,\n*    baz: \"baz\",\n*    ctx: this.context.value,\n*  };\n*}\n*\n*// BAD\n*class MyComponent extends React.Component {\n*  constructor(props, ctx) {\n*    this.state = {\n*      foo: props.foo,\n*      baz: \"baz\",\n*      ctx: ctx.value,\n*    };\n*  }\n*}\n*MyComponent.propTypes = {};\n*MyComponent.defaultProps = {};\n*2.3.4 [强制] component 必须有 render 方法，且 render 方法必须有返回\n*2.3.5 [强制] 使用解构获取 state、props、context 中的属性\n解释：\n*使代码可读性更高，更简洁\n示例：\n*// GOOD\n*render {\n*  const { props1, props2 } = this.props;\n*  const { state1, state2 } = this.state;\n*  ...\n*}\n*\n*// BAD\n*render {\n*  const props1 = this.props.props1;\n*  const props2 = this.props.props2;\n*  const state1 = this.props.state1;\n*  const state2 = this.props.state2;\n*  ...\n*}\n*2.4 Hooks 规范\n*2.4.1 [强制] 自定义 Hook 中必须直接或间接包含至少一个 React Hook\n解释：\n*如果不含任何 React Hook，说明这不是个 Hook 组件\n*2.4.2 [强制] 只在顶层调用 Hooks。不要在循环、条件语句或嵌套函数中调用 Hooks\n解释：\n*不要在循环、条件语句或嵌套函数中调用 Hooks，而只在 React 函数顶层调用 Hooks。Hook 是一个工作流程，在 React 中是一个链表数据结构，遵循这一规则，能够保证每次组件渲染时 Hooks 按同一顺序调用\n示例：\n*// GOOD\n*useEffect(() =\u003e {\n*  if (condition === 'a') {\n*    // do something when a\n*  }\n*}, []);\n*\n*// BAD\n*if (condition === 'a') {\n*  useEffect(() =\u003e {\n*    // do something when a\n*  }, []);\n*}\n*2.4.3 [建议] useEffect、useMemo、useCallback 的 deps 参数必须包含所有 callback 方法中用到的变量\n解释：\n*避免 callback 执行的上下文变量是过期的变量，而非预想中的变量\n示例：\n*// GOOD\n*const [state, setState] = useState({});\n*useEffect(() =\u003e {\n*  doSomething(state.a);\n*  doOtherThing(state.b);\n*}, [state.a, state.b]);\n*\n*// BAD\n*const [state, setState] = useState({});\n*useEffect(() =\u003e {\n*  doSomething(state.a);\n*  doOtherThing(state.b);\n*}, [state.a /*, state.b*/]);\n*2.4.4 [建议] useRef 的 React.Ref 对象，在访问时，不要用 someRef.current 访问，而应该先取到某个变量中访问\n解释：\n*ref 因为是个 mutable 对象，当在多个 effect 中设置值时，如果 effect 从 ref.currrent 中取值，会得到其他 effect 处理后的结果，这样会引入 effect 顺序依赖。所以，在访问 ref.current 前，直接用一个变量存取最后一次渲染的 ref 状态，避免在受到其他 effect 中变更的影响\n示例一：FunctionComponent Render 过程中的 reference 访问一致性处理方式\n*// GOOD\n*const prevPropsRef = useRef();\n*const prevProps = someRef.current;\n*useEffect(() =\u003e {\n*  prevPropsRef.current = props;  // 这行会修改 prevPropsRef 的值\n*}, [props]);\n*useEffect(() =\u003e {\n*  // 虽然前一个 Effect 修改了 prevPropsRef 的值，但是这里访问的是之前存取的变量，不受影响\n*  if (prevProps \u0026\u0026 prevProps.id !== props.id) {\n*    // do something\n*  }\n*}, [props]);\n*\n*// BAD\n*const prevPropsRef = useRef();\n*useEffect(() =\u003e {\n*  prevPropsRef.current = props;  // 这行会修改 prevPropsRef 的值\n*}, [props]);\n*useEffect(() =\u003e {\n*  // 因为上一个 Effect 会在这个之前执行并且把 prevPropsRef.current 更新成最新的 props\n*  // 所以此处永远为 true\n*  if (prevPropsRef.current \u0026\u0026 prevPropsRef.current.id !== props.id) {\n*    // do something\n*  }\n*}, [props]);\n示例二：useEffect 中 reference 访问一致性处理方式\n*// GOOD\n*const divRef = useRef(null);\n*useEffect(() =\u003e {\n*  const div = divRef.current\n*  if (!div) {\n*    return;\n*  }\n*  const someAsyncMethod = async() =\u003e {\n*    await a_few_moments_later();\n*    doSomethingWith(div);\n*  }\n*  someAsyncMethod();\n*\n*  const handler = () =\u003e xxxx;\n*  div.addEventListener('click', handler);\n*  return () =\u003e {\n*    div.removeEventListener('click', handler);\n*  }\n*}, []);\n*\n*// BAD\n*const divRef = useRef(null);\n*useEffect(() =\u003e {\n*  if (!divRef.current) {\n*    return;\n*  }\n*  const someAsyncMethod = async() =\u003e {\n*    await a_few_moments_later();\n*    // 这里 divRef.current 可能为 null\n*    doSomethingWith(divRef.current);\n*  }\n*  someAsyncMethod();\n*\n*  const handler = () =\u003e xxxx;\n*  divRef.current.addEventListener('click', handler);\n*  return () =\u003e {\n*    // React v17 开始，这里的 divRef 可能是 null\n*    // useEffect 返回的 callback 函数也是异步执行的\n*    // 如果使用 TypeScript，下面的代码可能自动补全 optionalChain\n*    // 此时如果 handler 中有 binding 外部闭包变量，将导致内存泄漏\n*    // 造成 detached DOM 常驻内存中\n*    divRef.current.removeEventListener('click', handler);\n*  }\n*}, []);\n*2.4.5 [强制] 只在 React 函数或自定义 hooks 中调用 hooks\n解释：\n*不要在一般函数中调用 Hooks。这条规则保证了组件的所有状态逻辑都能在组件代码中清晰可见\n*2.5 组件声明\n*2.5.1 [强制] 优先使用函数组件，当需要使用 componentDidCatch 时，可考虑使用 ClassComponent\n解释：\n*函数组件在React中有着特殊的地位，在将来也有可能得到更多的内部优化\n示例：\n*// Good\n*const NextNumber = ({value}) =\u003e \u003cspan\u003e{value + 1}\u003c/span\u003e;\n*\n*// Bad\n*class NextNumber {\n*  render() {\n*    const { value } = this.props;\n*    return \u003cspan\u003e{value + 1}\u003c/span\u003e;\n*  }\n*}\n*2.5.2 [建议] 使用箭头函数声明函数组件\n解释：\n*箭头函数具备更简洁的语法（无需 function 关键字），且可以在仅有一个语句时省去 return 造成的额外缩进\n示例：\n*// GOOD\n*export const MyComponent = props =\u003e { /* ... */ }\n*\n*// BAD\n*export function MyComponent(props) {/* ... */}\n*2.6 组件实现规范\n*2.6.1 [建议] 除顶层或路由级组件以外，所有组件均在概念上实现为纯组件\n解释：\n*本条规则并非要求组件一定继承自 PureComponent，“概念上的纯组件”的意思为一个组件在 props 和 state 没有变化（shallowEqual）的情况下，渲染的结果应保持一致，与纯函数类似。\n*\n*非纯组件具备向上的“传染性”，即一个包含非纯组件的组件也必须是非纯组件，依次沿组件树结构向上。由于非纯组件无法通过 shouldComponentUpdate 优化渲染性能且具备传染性，因此要避免在非顶层或路由组件中使用。\n*\n*如果需要在组件树的某个节点使用随机数、日期等非纯的数据，应当由顶层组件生成这个值并通过 props 传递下来。对于使用 Redux 等应用状态管理的系统，可以在应用状态中存放相关值（如 Redux使用 Action Creator 生成这些值并通过 Action 和 reducer 更新到 store 中）\n示例：\n*// GOOD\n*const RandomNumber = ({ number }) =\u003e (\u003cspan\u003e{number}\u003c/span\u003e);\n*// 顶层组件\n*class SomeContainer extends Component {\n*  state = {\n*    random: Math.random(),\n*  }\n*\n*  render () {\n*    return (\n*      \u003cRandomNumber number={this.state.random}/\u003e\n*    );\n*  }\n*}\n*\n*// BAD\n*const RandomNumber = () =\u003e \u003cspan\u003e{Math.random()}\u003c/span\u003e;\n*const Clock = () =\u003e \u003cspan\u003e{Date.time()}\u003c/span\u003e;\n*2.6.2 [建议] 为非继承自 PureComponent 的纯组件实现 shouldComponentUpdate 方法\n解释：\n*如果一个组件比较前后两次渲染状态的计算量比 render 小且不会因为父组件状态变更而频繁变更的话，shouldComponentUpdate 通常能减少不必要的重绘，带来较好的性能优化。但这不是绝对的。\n*如果滥用 PureComponent 和 shouldComponentUpdate 在很多情况下对性能也有负面影响，相关材料:\n*https://twitter.com/dan_abramov/status/759383530120110080\n*https://twitter.com/dan_abramov/status/820668074223353858\n*https://twitter.com/dan_abramov/status/1006871955126063105\n*2.6.3 [建议] 为函数组件添加 PureComponent 能力\n解释：\n*函数组件并非一定是纯组件，因此其 shouldComponentUpdate 的实现为 return true;，这可能导致额外的无意义渲染，因此推荐使用 React.memo 对函数组件进行优化\n示例：\n*// GOOD\n*import React, { memo } from 'react';  \n*function PercentageStat({ score = 0, total = Math.max(1, score) }) {  \n*  return (\n*    \u003cspan\u003e{ Math.round(score / total * 100) }%\u003c/span\u003e   \n*  );\n*}  \n*// 使用 `React.memo()` 包裹组件\n*export default memo(PercentageStat);\n*2.6.4 [建议] 优先使用 Functional Component + hooks\n解释：\n*在 React 版本允许的情况下，优先使用 hooks，代码量更少，逻辑更清晰；但不推荐将旧组件改为 hooks 实现\n*3 文件组织\n*3.1 组件目录\n*3.1.1 [强制] UI 组件以单个文件夹目录维护，有且只有一个 index 文件作为导出，业务组件（UI 组件 + 业务逻辑）用单文件维护\n解释：\n*UI 组件一般会有 css、icon、子 UI 文件，作为一个黑盒输出给业务使用，所以以目录方式维护结构更清晰。\n*业务组件使用单个文件维护可以做到强制抽离 UI 部分和逻辑部分的目的。\n示例：\n*// GOOD\n*/MyAwesomeComponent\n*/MyAwesomeComponent/index.(j|t)sx?\n*/MyAwesomeComponent/style.(le|s)?css\n*/MyAwesomeComponent/SomeOtherComponent.(j|t)sx?\n*\n*// BAD\n*/MyAwesomeComponent\n*/MyAwesomeComponent/index.(j|t)sx?\n*/MyAwesomeComponent/style.(le|s)?css\n*/MyAwesomeComponent/SomeOtherComponent.(j|t)sx?\n*3.1.2 [强制] 同一目录下不得拥有同名的 .js、. jsx 和 .tsx 文件\n解释：\n*在使用模块导入时，倾向于不添加后缀，如果存在同名但不同后缀的文件，构建工具将无法决定哪一个是需要引入的模块\n*示例：\n*\n*3.1.3 [强制] 组件文件使用一致的 .jsx、.tsx后缀\n*4 特定第三方库、特定场景规范\n*4.1 react-redux 相关\n*4.1.1 [建议] connect 参数使用建议命名\n解释：\n*connect 函数有四个可选参数：\n*mapStateToProps\n*mapDispatchToProps\n*mergeProps\n*options\n*这条规则强制所提供的参数遵循上述命名习惯\n示例：\n*// GOOD\n*connect(mapStateToProps, mapDispatchToProps, mergeProps)(TodoApp)\n*connect()(TodoApp)\n*\n*// BAD\n*connect(mapStateToProps, actionCreators)(TodoApp)\n*4.1.2 [建议] mapDispatchToProps 参数使用建议命名\n解释：\n*mapDispatchToProps 函数有两个可选参数：\n*dispatch\n*ownProps\n*提供的参数应遵循上述命名习惯\n示例：\n*// GOOD\n*const mapDispatchToProps = () =\u003e {...};\n*const mapDispatchToProps = (dispatch, ownProps) =\u003e {...};\n*const mapDispatchToProps = (dispatch, { prop1, prop2 }) =\u003e {...};\n*const mapDispatchToProps = (dispatch) =\u003e {...};\n*connect(mapStateToProps, mapDispatchToProps)(App);\n*\n*// BAD\n*const mapDispatchToProps = (anyOtherName) =\u003e {};\n*connect((state) =\u003e state, (anyOtherName) =\u003e {})(App);\n*4.1.3 [建议] mapStateToProps 参数使用建议命名\n解释：\n*mapStateToProps 函数有两个可选参数：\n*dispatch\n*ownProps\n*提供的参数应遵循上述命名习惯\n示例：\n*// GOOD\n*const mapStateToProps = (state, ownProps) =\u003e {...};\n*const mapStateToProps = state =\u003e {...};\n*const mapStateToProps = ({ isActive }) =\u003e {...};\n*connect(state =\u003e state, null)(App);\n*\n*// BAD\n*const mapStateToProps = (anyOtherName) =\u003e {...};\n*connect(function(anyOtherName) {...}, null)(App);\n*4.1.4 [建议] 禁止将整个 state 对象绑定到组件\n解释：\n*将整个 state 传入组件是错误的实践，这将导致非必要的渲染，应该传入组件所需的具体属性\n示例：\n*// GOOD\n*const mapStateToProps = () =\u003e {...};\n*const mapStateToProps = state =\u003e { isActive: state.isActive };\n*const mapStateToProps = ({ isActive }) =\u003e {...};\n*connect(state =\u003e ({ isActive: state.isActive }), null)(App);\n*\n*// BAD\n*const mapStateToProps = state =\u003e state;\n*const mapStateToProps = state =\u003e {\n*  return { state: state };\n*}\n*const mapStateToProps = state =\u003e ({ ...state });\n*connect(state =\u003e state, null)(App);\n*4.1.5 [建议] 如果使用的 React 版本支持 hook，在 FunctionComponent 中，使用 react-redux@^7.1.0 的 hook 来节省范式代码\n解释：\n*可以少打很多字\n示例：\n*// GOOD\n*import { useDispatch, useSelector } from 'react-redux';\n*\n*function MyInternalFunctionComponent(props) {\n*  const stateInStore = useSelector(state =\u003e pick(state, fields));\n*  const dispatch = useDispatch();\n*  const onChange = useCallback(v =\u003e {\n*    dispatch(createChangeAction(props.doSomething(v)));\n*  }, [dispatch]);\n*  return /* My JSX */\n*}\n*\n*// BAD\n*import { connect } from 'react-redux';\n*\n*function MyInternalFunctionComponent(props) {\n*  return /* My JSX */\n*}\n*const mapStateToProps = state =\u003e pick(state, fields);\n*const mapDispatchToProps = (dispatch, ownProps) =\u003e ({\n*  onChange(v) {\n*    dispatch(createChangeAction(ownProps.doSomething(v)));\n*  }\n*});\n*export const MyFunctionComponent = connect(\n*  mapStateToProps,\n*  mapDispatchToProps,\n*)(MyInternalFunctionComponent);\n*4.2 styled-components\n*4.2.1 [强制] 禁止在 Array map 中使用非 static styled-components，应使用 attrs\n解释：\n*styled-components 会为每 props 映射出来的 style 新增一种 class，用 \u003cstyle\u003e 标签插入到 html 文档中。当 props 在 map 循环中有很多影响 style 的值时，会导致性能问题\n*这种情况应使用 attrs 内联或者 JSX 中插入 style 属性\n示例：\n*// GOOD\n*const Row = styled.li.attr(props =\u003e ({ style: { height: props.imgHeight } }))`\n*  // some style\n*`;\n*// snip:start\n*{data.map(datum =\u003e (\n*  \u003cRow key={datum.src} height={datum.imgHeight}\u003e\u003cimg src={datum.src} /\u003e\u003c/Row\u003e\n*))}\n*// snip:end\n*\n*// BAD\n*const Row = styled.li`\n*  // some style\n*  height: ${props =\u003e props.height}px; // 如果有 100 个 height，会生成 100 个 class\n*`;\n*// snip:start\n*{data.map(datum =\u003e (\n*  \u003cRow key={datum.src} height={datum.imgHeight}\u003e\u003cimg src={datum.src} /\u003e\u003c/Row\u003e\n*))}\n*// snip:end\n*4.2.2 [强制] 禁止在 render、React.FC 中使用 styled 声明新的 component\n解释：\n*styled 会返回一个新的 ComponentType 导致重绘\n*即使使用 useMemo，或者任何自定义的缓存方式，也无法保障 SSR 时候，生成的 styled-components 的 componentId 一致（这会导致 html 输出的 class 和 CSR 恢复时 class 名不一致，造成样式丢失）\n示例：\n*// GOOD\n*const Div = styled.div`\n*  // some css`;\n*const SomeComponent = () =\u003e {\n*  return \u003cDiv {...someProps} /\u003e;\n*}\n*\n*// BAD\n*const SomeComponent = () =\u003e {\n*  const Div = React.useMemo(() =\u003e {\n*    return styled.div`\n*      // some css\n*    `;\n*  }, []);\n*  return \u003cDiv {...someProps} /\u003e;\n*}\n*4.3 antd\n*4.3.1 [强制] Form 组件禁止使用 getFieldProps getFieldDecorator 声明了 ReactElement 但不渲染\n解释：\n*rc-form 字段回收时机问题\n*rc-form 回收字段的机制是在 getFieldProps 或者 getFieldDecorator 时注入 ref 回调函数，如果组件 unmount，会触发 ref(null)，此时会把相应字段删除。如果代码用 getFieldProps 或者 getFieldDecorator 生成了 field，但没有输出在 ReactDOMTree 上，会导致不会执行 ref 回调。这时会发现字段无法正常删除\n示例：\n*// GOOD\n*const { type } = this.props;\n*return type === 'a'\n*  ? getFieldDecorator('fieldA')(\u003cInput /\u003e)\n*  : getFieldDecorator('fieldB')(\u003cCheckbox /\u003e);\n*\n*// BAD\n*const { type } = this.props;\n*const elem1 = getFieldDecorator('fieldA')(\u003cInput /\u003e);\n*const elem2 = getFieldDecorator('fieldB')(\u003cCheckbox /\u003e);\n*return type === 'a' ? elem1 : elem2;\n*4.3.2 [强制] Form 组件禁止 getFieldProps getFieldDecorator 使用重复 id\n解释：\n*rc-form 字段回收时机问题\n*跟上一条原因差不多，当其中一个声明的组件执行 unmount，对应的字段删除了。但是，因为 unmount 声明周期是在所有 component render 之后，这是后页面显示的内容还在，但是值其实已经被删除了，导致视图和 state 不一致，增加排查问题难度\n示例：\n*// GOOD\n*// 如果需要表单字段联动，可以在 onchange 时触发另一个字段变更\n*\u003c\u003e\n*  {getFieldDecorator('fieldA-1', {\n*    onChange(v) {\n*      form.setFieldsValue({ 'fieldA-1': v, 'fieldA-2': v });\n*    }\n*  })(\u003cInput /\u003e)}\n*  {getFieldDecorator('fieldA-2', {\n*    onChange(v) {\n*      form.setFieldsValue({ 'fieldA-1': v, 'fieldA-2': v });\n*    }\n*  })(\u003cInput /\u003e)}\n*\u003c/\u003e\n*\n*// BAD\n*\u003c\u003e\n*  {getFieldDecorator('fieldA-1')(\u003cInput1 /\u003e)}  // 相同的 field 会导致下面 Input2 因为 someCondition 被删除时，fieldA-1 丢失，而页面依然显示\n*  {someCondition \u0026\u0026 getFieldDecorator('fieldA-1')(\u003cInput2 /\u003e)}\n*\u003c/\u003e\n*4.3.3 [强制] Form 组件禁止切换组件 type 渲染同一个 id\n示例：\n*// GOOD\n*\u003c\u003e\n*  {type === 'Input' \u0026\u0026 getFieldDecorator('fieldInput')(\u003cInput /\u003e)}\n*  {type === 'Select' \u0026\u0026 getFieldDecorator('fieldSelect')(\u003cSelect /\u003e)}  \n*\u003c/\u003e\n*\n*// BAD\n*\u003c\u003e\n*  {type === 'Input' \u0026\u0026 getFieldDecorator('field')(\u003cInput /\u003e)}\n*  {type === 'Select' \u0026\u0026 getFieldDecorator('field')(\u003cSelect /\u003e)}  \n*\u003c/\u003e\n*4.4 TypeScript\n*4.4.1 [建议] 尽量提供完整的泛型\n解释:\n*React 的 api 大量依赖泛型，提供尽量完整的泛型有助于代码提示及错误及早发现\n示例：\n*// GOOD\n*interface Props {\n*  x: number\n*}\n*function A(props: Props) {}\n*class B extends Component\u003cProps, State\u003e {}\n*\n*// BAD\n*function A(props: any) {}\n*class B extends Component\u003cany, any\u003e {}\n*5 HTML 相关\n*5.1 a11y 相关\n*5.1.1 [建议] 如果文本中有 emoji 字符，使用 \u003cspan\u003e 标签包裹它，并设置role=\"img\" aria-label=\"emojiname\"\n解释：\n*无障碍阅读\n示例：\n*// GOOD\n*\u003cspan role=\"img\" aria-label=\"Snowman\"\u003e\u0026#9731;\u003c/span\u003e\n*\u003cspan role=\"img\" aria-label=\"Panda\"\u003e🐼\u003c/span\u003e\n*\u003cspan role=\"img\" aria-labelledby=\"panda1\"\u003e🐼\u003c/span\u003e\n*\n*// BAD\n*\u003cspan\u003e🐼\u003c/span\u003e\n*\u003ci role=\"img\" aria-label=\"Panda\"\u003e🐼\u003c/i\u003e\n*5.1.2 [建议] input[type=image]、img、area、object 标签添加必要的内容说明属性（alt、title、aria-label）\n解释：\n*无障碍阅读\n示例：\n*// GOOD\n*\u003cimg src=\"foo\" alt=\"Foo eating a sandwich.\" /\u003e\n*\u003cobject aria-label=\"foo\" /\u003e\n*\u003cobject title=\"An object\" /\u003e\n*\u003carea aria-label=\"foo\" /\u003e\n*\u003carea aria-labelledby=\"id1\" /\u003e\n*\u003carea alt=\"This is descriptive!\" /\u003e\n*\u003cinput type=\"image\" alt=\"This is descriptive!\" /\u003e\n*\u003cinput type=\"image\" aria-label=\"foo\" /\u003e\n*\u003cinput type=\"image\" aria-labelledby=\"id1\" /\u003e\n*\n*// BAD\n*\u003cimg src=\"foo\" /\u003e\n*\u003cimg {...props} /\u003e\n*\u003cobject {...props} /\u003e\n*\u003carea {...props} /\u003e\n*\u003cinput type=\"image\" {...props} /\u003e\n*5.1.3 [建议] \u003ca\u003e 标签必须有内容\n示例：\n*// GOOD\n*\u003ca\u003eAnchor Content!\u003c/a\u003e\n*\u003ca\u003e\u003cTextWrapper /\u003e\u003c/a\u003e\n*\u003ca dangerouslySetInnerHTML={{ __html: 'foo' }} /\u003e\n*\n*// BAD\n*\u003ca /\u003e\u003ca\u003e\n*\u003cTextWrapper aria-hidden /\u003e\u003c/a\u003e\n*5.1.4 [建议] \u003ca\u003e 标签如果没有有意义的 href，建议换成 button\n解释：\n*语义化\n示例：\n*// GOOD\n*\u003cbutton onClick={foo} \u003ePerform action\u003c/button\u003e\n*\u003cbutton href=\"#\" onClick={foo} \u003ePerform action\u003c/button\u003e\n*\u003cbutton onClick={foo} \u003ePerform action\u003c/button\u003e\n*\n*// BAD\n*\u003ca href=\"javascript:void(0)\" onClick={foo} \u003ePerform action\u003c/a\u003e\n*\u003ca href=\"#\" onClick={foo} \u003ePerform action\u003c/a\u003e\n*\u003ca onClick={foo} \u003ePerform action\u003c/a\u003e\n\n*关于 Hooks\nHooks 目前还没有形成很好的范式代码和实践，这里先暂时放一下比较基础的闭坑资料：\n*useCallback hell问题总结 \n*致命的 useCallback/useMemo（翻译） \n \n\n*\n\n\n\n"
  }
    const typescript = {
  "text":
  "TypeScript 代码规范\n所有的 [强制] 和 [建议] 规则都有对应的 eslint rule 与之对应，不然就没有意义。\n [强制] 中定义的规则，都是为了避免产生 Runtime error 而存在。\n*启用这些规则来避免 Runtime error 的前置条件是所有被这些规则约束的 TypeScript 代码都能正常的被 tsc 检查类型信息。也就是说如果将这些规则应用到 JS 文件上可能没有效果，因为很多规则都依赖类型信息进行规则检查。\n\n*强制\n* 函数和方法重载的声明和实现必须放在一起\n保障代码可读性。\n*❌ 错误例子 \n\n*declare function foo(s: string): void;\n*declare function foo(n: number): void;\n*declare function bar(): void;\n*declare function foo(sn: string | number): void;\n*\n*✅ 正确例子\n\n*declare function foo(s: string): void;\n*declare function foo(n: number): void;\n*declare function foo(sn: string | number): void;\n*declare function bar(): void;\n*eslint 规则\n*@typescript-eslint/adjacent-overload-signatures \n* 不允许 await 非 Thenable 的语句\n对 await 的使用不加以限制经常会造成运行时错误，比如 await 一个返回 Promise 的函数但是忘了 call 它。\n*❌ 错误例子 \n\n*await 'value';\n*\n*const createValue = () =\u003e 'value';\n*await createValue();\n*✅ 正确例子 \n\n*await Promise.resolve('value');\n*\n*const createValue = async () =\u003e 'value';\n*await createValue();\n*eslint 规则\n\n*@typescript-eslint/await-thenable\n*不允许使用 String Number Boolean Function any 等非安全类型\n应该用 string number boolean (...args: T[]) =\u003e R 等类型代替。\n*❌ 错误例子 \n\n*const str: String = 'foo';\n*const bool: Boolean = true;\n*const num: Number = 1;\n*const symb: Symbol = Symbol('foo');\n*const func: Function = () =\u003e 1;\n*\n*const lowerObj: object = {};\n*const capitalObj1: Object = 1;\n*const capitalObj2: Object = { a: 'string' };\n*const curly1: {} = 1;\n*const curly2: {} = { a: 'string' };\n*✅ 正确例子 \n\n*const str: string = 'foo';\n*const bool: boolean = true;\n*const num: number = 1;\n*const symb: symbol = Symbol('foo');\n*const func: () =\u003e number = () =\u003e 1;\n*\n*const lowerObj: Record\u003cstring, unknown\u003e = {};\n*const capitalObj1: number = 1;\n*const capitalObj2: { a: string } = { a: 'string' };\n*const curly1: number = 1;\n*const curly2: Record\u003c'a', string\u003e = { a: 'string' };\n*eslint 规则\n\n*'@typescript-eslint/ban-types':\n*[\n*  'error',\n*  {\n*    'types':\n*      {\n*        'String': { 'message': 'Use string instead', 'fixWith': 'string' },\n*        'Number': { 'message': 'Use number instead', 'fixWith': 'number' },\n*        'Boolean': { 'message': 'Use boolean instead', 'fixWith': 'boolean' },\n*        'Symbol': { 'message': 'Use symbol instead', 'fixWith': 'symbol' },\n*        'Function': { 'message': 'Use explicit type instead' },\n*        'Object': { 'message': 'Object type is disallowed' },\n*        'object': { 'message': 'object type is disallowed' },\n*        '{}': { 'message': '{} type is disallowed' },\n*        'any': { 'message': 'Use explicit type instead' },\n*      },\n*  },\n*]\n*声明对象方法的时候，只能用声明函数属性(Property with function type)的形式，而不能用声明对象方法(Object method)的形式声明\n在 TypeScript 中，声明对象上的方法有两种不同的形式，它们类型相同但是行为有一些差异:\n\n声明为方法:\n\n*// method shorthand syntax\n*interface T1 {\n*  func(arg: string): number;\n*}\n\n声明为函数:\n\n*// regular property with function type\n*interface T2 {\n*  func: (arg: string) =\u003e number;\n*}\n\n在 TypeScript 开启了 strict 选项之后，会默认打开 strictFunctionTypes 选项。在开启这个选项之后，方法的参数是双向逆变的，函数的参数是逆变的：https://github.com/microsoft/TypeScript/pull/18654\n\n参数双向逆变相对与逆变更为宽松，但是也可能会引发问题:\n\n*// method shorthand syntax\n*interface T1 {\n*  func(arg: HTMLElement): number;\n*}\n*// regular property with function type\n*interface T2 {\n*  func: (arg: HTMLElement) =\u003e number;\n*}\n*const func = (arg: HTMLCanvasElement): number =\u003e {\n*  return arg.toDataURL().length\n*}\n*\n*const t1: T1 = {\n*    // no type error, but will cause runtime error\n*    func,\n*}\n*const t2: T2 = {\n*    // ts error because `HTMLElement` is not compatible with `HTMLCanvasElement`\n*    func,\n*}\n\n关于逆变 和 协变 可以看这里:\nhttps://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/covariance-contravariance/\nhttps://jkchao.github.io/typescript-book-chinese/tips/covarianceAndContravariance.html#%E4%B8%80%E4%B8%AA%E6%9C%89%E8%B6%A3%E7%9A%84%E9%97%AE%E9%A2%98\n https://github.com/Microsoft/TypeScript/wiki/FAQ#why-are-function-parameters-bivariant\n*❌ 错误例子\n\n*interface T1 {\n*  func(arg: string): number;\n*}\n*type T2 = {\n*  func(arg: boolean): void;\n*};\n*interface T3 {\n*  func(arg: number): void;\n*  func(arg: string): void;\n*  func(arg: boolean): void;\n*}\n*✅ 正确例子 \n\n*interface T1 {\n*  func: (arg: string) =\u003e number;\n*}\n*type T2 = {\n*  func: (arg: boolean) =\u003e void;\n*};\n*// this is equivalent to the overload\n*interface T3 {\n*  func: ((arg: number) =\u003e void) \u0026\n*    ((arg: string) =\u003e void) \u0026\n*    ((arg: boolean) =\u003e void);\n*}\n*\n*declare function Func(arg: number): void\n*declare function Func(arg: string): void\n*declare function Func(arg: boolean): void\n*interface T4 {\n*  func: typeof Func;\n*}\n*eslint 规则\n\n*@typescript-eslint/method-signature-style\n*不允许出现悬垂（floating promise） Promise\n所有 PromiseLike 的对象的异常都应该被正确的处理，包括使用 catch, await 以及 return。既没有 catch 也没有await 也没有 return 的 PromiseLike 对象被称为悬垂 Promise。\n*❌ 错误例子 \n\n*const promise = new Promise((resolve, reject) =\u003e resolve('value'))\n*promise\n*\n*async function returnsPromise() {\n*  return 'value'\n*}\n*returnsPromise().then(() =\u003e {})\n*\n*Promise.reject('value').catch()\n*\n*Promise.reject('value').finally()\n*✅ 正确例子 \n\n*const runPromise = () =\u003e new Promise((resolve, reject) =\u003e resolve('value'))\n*await runPromise()\n*\n*async function returnsPromise() {\n*  return 'value'\n*}\n*returnsPromise().then(\n*  () =\u003e {},\n*  () =\u003e {},\n*)\n*\n*Promise.reject('value').catch(() =\u003e {})\n*\n*Promise.reject('value').finally(() =\u003e {})\n*eslint 规则\n\n*@typescript-eslint/no-floating-promises\n*不允许出现 eval()  类型的调用\n出于安全和性能方面的考虑，eval 以及与其有相似作用的调用应该被禁用。\n\n*// setTimeout/setInterval/setImmediate/execScript\n*setTimeout('alert(`Hi!`);', 100);\n*const fn = new Function('a', 'b', 'return a + b');\n*❌ 错误例子 \n\n*setTimeout('alert(`Hi!`);', 100);\n*\n*setInterval('alert(`Hi!`);', 100);\n*\n*setImmediate('alert(`Hi!`)');\n*\n*execScript('alert(`Hi!`)');\n*\n*window.setTimeout('count = 5', 10);\n*\n*window.setInterval('foo = bar', 10);\n*\n*const fn = '() = {}';\n*setTimeout(fn, 100);\n*\n*const fn = () =\u003e {\n*  return 'x = 10';\n*};\n*setTimeout(fn(), 100);\n*\n*const fn = new Function('a', 'b', 'return a + b');\n*✅ 正确例子 \n\n*setTimeout(function () {\n*  alert('Hi!');\n*}, 100);\n*\n*setInterval(function () {\n*  alert('Hi!');\n*}, 100);\n*\n*setImmediate(function () {\n*  alert('Hi!');\n*});\n*\n*execScript(function () {\n*  alert('Hi!');\n*});\n*\n*const fn = () =\u003e {};\n*setTimeout(fn, 100);\n*\n*const foo = {\n*  fn: function () {},\n*};\n*setTimeout(foo.fn, 100);\n*setTimeout(foo.fn.bind(this), 100);\n*\n*class Foo {\n*  static fn = () =\u003e {};\n*}\n*\n*setTimeout(Foo.fn, 100);\n*eslint 规则\n\n*@typescript-eslint/no-implied-eval \n*不允许 for-in Array\n对 Array 使用 for-in 语句会产生一些非预期的问题，比如 for-in 会跳过 Array 的 空洞 ，并且会将数组的索引作为 字符串 取出，这可能不是你预期的结果。\n*❌ 错误例子 \n\n*for (const x in [3, 4, 5]) {\n*  console.log(x);\n*}\n*✅ 正确例子 \n\n*array.forEach((value, index) =\u003e { ... });\n*for (const [index, value] of array.entries()) { ... }\n*for (let i = 0; i \u003c array.length; i++) { ... }\n*eslint 规则\n\n*@typescript-eslint/no-for-in-array \n*不允许定义 Class 类型 的 new 方法 和 Interface 类型 的 constructor 方法 \n在 Class  类型上声明 new 和 Interface 类型上声明 constructor 都是误用。\n*❌ 错误例子 \n\n*class C {\n*  new(): C;\n*}\n*\n*interface I {\n*  new (): I;\n*  constructor(): void;\n*}\n*✅ 正确例子\n\n*class C {\n*  constructor() {}\n*}\n*interface I {\n*  new (): C;\n*}\n*eslint  规则\n\n*@typescript-eslint/no-misused-new\n*不允许在不合适的地方使用 Promise 值\n在一些不合适的情况下误用 Promise 值， TypeScript  编译器不会给出任何错误或者警告信息。\n*❌ 错误例子 \n\n*const promise = Promise.resolve('value');\n*\n*if (promise) {\n*  // Do something\n*}\n*\n*const val = promise ? 123 : 456;\n*\n*while (promise) {\n*  // Do something\n*}\n\n以及\n\n*// callback 返回值为 void，不允许为 Promise\n*\n*[1, 2, 3].forEach(async value =\u003e {\n*  await doSomething(value);\n*});\n*\n*new Promise(async (resolve, reject) =\u003e {\n*  await doSomething();\n*  resolve();\n*});\n*\n*const eventEmitter = new EventEmitter();\n*eventEmitter.on('some-event', async () =\u003e {\n*  await doSomething();\n*});\n\n*✅ 正确例子\n\n*const promise = Promise.resolve('value');\n*\n*if (await promise) {\n*  // Do something\n*}\n*\n*const val = (await promise) ? 123 : 456;\n*\n*while (await promise) {\n*  // Do something\n*}\n*\n*await Promise.all([1, 2, 3].map(doSomething))\n*\n*for (const value of [1, 2, 3]) {\n*  await doSomething(value);\n*}\n*\n*new Promise((resolve, reject) =\u003e {\n*  // Do something\n*  resolve();\n*});\n*\n*const eventEmitter = new EventEmitter();\n*eventEmitter.on('some-event', () =\u003e {\n*  doSomething();\n*});\n\n*eslint 规则\n*@typescript-eslint/no-misused-promises\n\n*不允许使用 namespace\nnamespace 是 ES Module 规范出现之前的过时的模块组织方式，不应该再使用。\n*❌ 错误例子 \n\n*module foo {}\n*namespace foo {}\n\n*✅ 正确例子\n\n*export function foo() { }\n\n*eslint 规则\n*@typescript-eslint/no-namespace\n\n*不允许在 optional chain 语句后面加非空断言\noptional chain 会在可选属性为 nullish 值的时候产生 undefined 值，在 optional chain 后面直接用非空断言非常危险。\n*❌ 错误例子 \n\n*foo?.bar!;\n*foo?.bar!.baz;\n*foo?.bar()!;\n*foo?.bar!();\n*foo?.bar!().baz;\n*✅ 正确例子\n\n*foo?.bar;\n*foo?.bar?.baz;\n*foo?.bar();\n*foo?.bar();\n*foo?.bar().baz;\n\n*eslint 规则\n*@typescript-eslint/no-non-null-asserted-optional-chain\n\n* 不允许 throw 非 Error 值\n如果直接 throw 非 error 对象，在代码出问题的时候就无法获取调用栈等有用的调试信息。\n*❌ 错误例子 \n\n*throw 'error';\n*throw 0;\n*throw undefined;\n*throw null;\n*const err = new Error();\n*throw 'an ' + err;\n*const err = new Error();\n*throw `${err}`;\n*const err = '';\n*throw err;\n*function err() {\n*  return '';\n*}\n*throw err();\n*const foo = {\n*  bar: '',\n*};\n*throw foo.bar;\n*✅ 正确例子 \n\n*throw new Error();\n*throw new Error(\"error\");\n*const e = new Error(\"error\");\n*throw e;\n*try {\n*    throw new Error(\"error\");\n*} catch (e) {\n*    throw e;\n*}\n*const err = new Error();\n*throw err;\n*function err() {\n*  return new Error();\n*}\n*throw err();\n*const foo = {\n*  bar: new Error();\n*}\n*throw foo.bar;\n*class CustomError extends Error {\n*  // ...\n*};\n*throw new CustomError();\n\n*eslint 规则\n*@typescript-eslint/no-throw-literal\n\n*优先使用 nullish coalescing\n在没有 nullish coalescing 的时候，我们通常使用 || 在赋值语句或者三元运算符中来判断某个变量是否为空，但是这个用法在一些场景下可能会产生非预期行为。\n*❌ 错误例子 \n\n*const emptyString: string | null | undefined = '';\n*const nullish1 = emptyString || 'unsafe';\n*✅ 正确例子 \n\n*const emptyString: string | null | undefined = '';\n*const logical1 = emptyString ?? 'unsafe';\n*eslint 规则\n*@typescript-eslint/prefer-nullish-coalescing\n\n*switch 语句需要覆盖所有可能情况\n联合类型可能会出现很多种值，在 switch 语句中使用联合类型的值的时候很容易忘记考虑某种类型的值。\n在一些特殊的场景中，如果我们只关心其中某一个值的情况，那应该给 switch 语句显式的加上 default 语句。\n*❌ 错误例子 \n\n*type Day =\n*  | 'Monday'\n*  | 'Tuesday'\n*  | 'Wednesday'\n*  | 'Thursday'\n*  | 'Friday'\n*  | 'Saturday'\n*  | 'Sunday';\n*const day = 'Monday' as Day;\n*\n*let result = 0;\n*\n*switch (day) {\n*  case 'Monday': {\n*    result = 1;\n*    break;\n*  }\n*}\n*✅ 正确例子 \n\n*type Day =\n*  | 'Monday'\n*  | 'Tuesday'\n*  | 'Wednesday'\n*  | 'Thursday'\n*  | 'Friday'\n*  | 'Saturday'\n*  | 'Sunday';\n*const day: Day = 'Monday';\n*let result = 0;\n*switch (day) {\n*  case 'Monday': {\n*    result = 1;\n*    break;\n*  }\n*  case 'Tuesday': {\n*    result = 2;\n*    break;\n*  }\n*  case 'Wednesday': {\n*    result = 3;\n*    break;\n*  }\n*  case 'Thursday': {\n*    result = 4;\n*    break;\n*  }\n*  case 'Friday': {\n*    result = 5;\n*    break;\n*  }\n*  case 'Saturday': {\n*    result = 6;\n*    break;\n*  }\n*  case 'Sunday': {\n*    result = 7;\n*    break;\n*  }\n*}\n*\n*type Day =\n*  | 'Monday'\n*  | 'Tuesday'\n*  | 'Wednesday'\n*  | 'Thursday'\n*  | 'Friday'\n*  | 'Saturday'\n*  | 'Sunday';\n*const day: Day = 'Monday';\n*let result = 0;\n*switch (day) {\n*  case 'Monday': {\n*    result = 1;\n*    break;\n*  }\n*  default: {\n*    result = 42;\n*  }\n*}\n*eslint 规则\n*@typescript-eslint/switch-exhaustiveness-check\n*禁止将没有 await 的函数标记为 async \n将没有 await 语句 的函数标记为 async 会改变函数的运行时行为。\n*❌ 错误例子 \n\n*async function returnsPromise1() {\n*  return 1;\n*}\n*✅ 正确例子 \n\n*async function returnsPromise1() {\n*  await deleteResource();\n*  return 1\n*}\n*\n*const returnsPromise2 = () =\u003e returnsPromise1();\n*eslint 规则\n*@typescript-eslint/require-await\n*eslint 规则配置\n*{\n*  // note you must disable the base rule as it can report incorrect errors\n*  \"require-await\": \"off\",\n*  \"@typescript-eslint/require-await\": \"error\"\n*}\n*在 async 函数的 try catch 语句中总是返回 awaited promise\nasync 函数中，promise 被 await 后返回能在出错的时候产生更友好的错误栈信息，而且能更好的被 try {} catch {} 语句处理。\n*❌ 错误例子 \n\n*async function invalidInTryCatch1() {\n*  try {\n*    return Promise.resolve('try');\n*  } catch (e) {}\n*}\n*\n*async function invalidInTryCatch2() {\n*  return await Promise.resolve('try');\n*}\n*\n*async function invalidInTryCatch3() {\n*  return await 'value';\n*}\n\n*✅ 正确例子 \n\n*async function validInTryCatch1() {\n*  try {\n*    return await Promise.resolve('try');\n*  } catch (e) {\n*    console.error(e)\n*    return await Promise.resolve(null)\n*  }\n*}\n*\n*async function validInTryCatch2() {\n*  return Promise.resolve('try');\n*}\n*eslint 规则\n*@typescript-eslint/return-await\n*eslint 用法\n*{\n*  // note you must disable the base rule as it can report incorrect errors\n*  \"no-return-await\": \"off\",\n*  \"@typescript-eslint/return-await\": \"error\"\n*}\n\n*禁止 ts-ignore 与 ts-nocheck 注释\n无论在哪种情况下，跳过类型检查都是得不偿失的极度危险行为。\n*eslint 规则\n*'@typescript-eslint/ban-ts-comment':\n*    [2, { 'ts-expect-error': false, 'ts-ignore': true, 'ts-nocheck': true, 'ts-check': false }]\n*建议\n*默认省略除了 constructor 参数以外的 public 修饰\nclass 上的属性和方法默认 public ，加修饰语义重复。\n*❌ 错误例子 \n\n*class Animal {\n*  public constructor(public breed, name) {\n*    // Parameter property and constructor\n*    this.animalName = name;\n*  }\n*  public animalName: string; // Property\n*  public get name(): string {\n*    // get accessor\n*    return this.animalName;\n*  }\n*  public set name(value: string) {\n*    // set accessor\n*    this.animalName = value;\n*  }\n*  public walk() {\n*    // method\n*  }\n*}\n*✅ 正确例子 \n\n*class Animal {\n*  constructor(protected breed, name) {\n*    // Parameter property and constructor\n*    this.name = name;\n*  }\n*  private animalName: string; // Property\n*  get name(): string {\n*    // get accessor\n*    return this.animalName;\n*  }\n*  private set name(value: string) {\n*    // set accessor\n*    this.animalName = value;\n*  }\n*  protected walk() {\n*    // method\n*  }\n*}\n*eslint 规则\n\n*'@typescript-eslint/explicit-member-accessibility':\n*[\n*  'error',\n*  {\n*    accessibility: 'explicit',\n*    overrides:\n*      {\n*        accessors: 'no-public',\n*        constructors: 'no-public',\n*        methods: 'no-public',\n*        properties: 'no-public',\n*        parameterProperties: 'explicit',\n*      },\n*  },\n*]\n\n*不允许给能自动推断出类型的 primitive 类型变量额外添加类型声明\n在参数、变量和属性声明中，如果语句已经能让类型系统自动推断出类型信息，并且类型是原始类型，则不需要再给这些参数/变量/属性声明类型。\n*❌ 错误例子 \n\n*const a: bigint = 10n;\n*const a: bigint = -10n;\n*const a: bigint = BigInt(10);\n*const a: bigint = -BigInt(10);\n*const a: boolean = false;\n*const a: boolean = true;\n*const a: boolean = Boolean(null);\n*const a: boolean = !0;\n*const a: number = 10;\n*const a: number = +10;\n*const a: number = -10;\n*const a: number = Number('1');\n*const a: number = +Number('1');\n*const a: number = -Number('1');\n*const a: number = Infinity;\n*const a: number = +Infinity;\n*const a: number = -Infinity;\n*const a: number = NaN;\n*const a: number = +NaN;\n*const a: number = -NaN;\n*const a: null = null;\n*const a: RegExp = /a/;\n*const a: RegExp = RegExp('a');\n*const a: RegExp = new RegExp('a');\n*const a: string = 'str';\n*const a: string = `str`;\n*const a: string = String(1);\n*const a: symbol = Symbol('a');\n*const a: undefined = undefined;\n*const a: undefined = void someValue;\n*\n*class Foo {\n*  prop: number = 5;\n*}\n*\n*function fn(a: number = 5, b: boolean = true) {}\n*✅ 正确例子 \n\n*const a = 10n;\n*const a = -10n;\n*const a = BigInt(10);\n*const a = -BigInt(10);\n*const a = false;\n*const a = true;\n*const a = Boolean(null);\n*const a = !0;\n*const a = 10;\n*const a = +10;\n*const a = -10;\n*const a = Number('1');\n*const a = +Number('1');\n*const a = -Number('1');\n*const a = Infinity;\n*const a = +Infinity;\n*const a = -Infinity;\n*const a = NaN;\n*const a = +NaN;\n*const a = -NaN;\n*const a = null;\n*const a = /a/;\n*const a = RegExp('a');\n*const a = new RegExp('a');\n*const a = 'str';\n*const a = `str`;\n*const a = String(1);\n*const a = Symbol('a');\n*const a = undefined;\n*const a = void someValue;\n*\n*class Foo {\n*  prop = 5;\n*}\n*\n*function fn(a = 5, b = true) {}\n*\n*function fn(a: number, b: boolean, c: string) {}\n*eslint 规则\n\n*@typescript-eslint/no-inferrable-types\n\n*不允许在范型和返回值之外的地方使用 void 类型\nvoid  的语义是 不返回任何值，这个语义意味着你不可以将它在返回值类型以外的场景使用，如果要表达值类型，请用undefined 来替代。\n*❌ 错误例子\n\n*type PossibleValues = string | number | void;\n*type MorePossibleValues = string | ((number \u0026 any) | (string | void));\n*\n*function logSomething(thing: void) {}\n*function printArg\u003cT = void\u003e(arg: T) {}\n*\n*logAndReturn\u003cvoid\u003e(undefined);\n*\n*interface Interface {\n*  lambda: () =\u003e void;\n*  prop: void;\n*}\n*\n*class MyClass {\n*  private readonly propName: void;\n*}\n*✅ 正确例子\n\n*type NoOp = () =\u003e void;\n*\n*function noop(): void {}\n*\n*let trulyUndefined = void 0;\n*\n*async function promiseMeSomething(): Promise\u003cvoid\u003e {}\n*\n*type stillVoid = void | never;\n*eslint 规则\n*@typescript-eslint/no-invalid-void-type\n\n*避免非必要的布尔值比较\n判断一个变量是否与字面量布尔值相等在很多情况下是非必要的，大多数情况下可以直接用这个变量作为判断的条件。\n在变量的类型为联合类型(Union type)的时候，这条规则不会限制判断变量是否与字面量布尔值相等。\n*❌ 错误例子 \n\n*declare const someCondition: boolean;\n*if (someCondition === true) {\n*}\n*✅ 正确例子 \n\n*declare const someCondition: boolean;\n*if (someCondition) {\n*}\n*\n*declare const someObjectBoolean: boolean | Record\u003cstring, unknown\u003e;\n*\n*if (someObjectBoolean === true) {\n*}\n*\n*declare const someStringBoolean: boolean | string;\n*\n*if (someStringBoolean === true) {\n*}\n*eslint 规则\n*@typescript-eslint/no-unnecessary-boolean-literal-compare\n\n*在只读访问数组的元素的情况下，倾向于用 for-of 替代标准的 for 循环\nfor-of 相对于标准的 for 循环更简洁易读。\n*❌ 错误例子 \n\n*for (let i = 0; i \u003c arr.length; i++) {\n*  console.log(arr[i]);\n*}\n*✅ 正确例子 \n\n*for (const x of arr) {\n*  console.log(x);\n*}\n*for (let i = 0; i \u003c arr.length; i++) {\n*  // i is used to write to arr, so for-of could not be used.\n*  arr[i] = 0;\n*}\n*for (let i = 0; i \u003c arr.length; i++) {\n*  // i is used independent of arr, so for-of could not be used.\n*  console.log(i, arr[i]);\n*}\n\n*eslint 规则\n*@typescript-eslint/prefer-for-of\n*倾向使用 optional chain \noptional chain 可以很方便的在对象上安全的取值，代码更简洁也更易读。\n*❌ 错误例子 \n\n*type T = {\n*  a?: {\n*    b?: {\n*      c: string;\n*      method?: () =\u003e void;\n*    };\n*  };\n*};\n*\n*function myFunc(foo: T | null) {\n*  return foo \u0026\u0026 foo.a \u0026\u0026 foo.a.b \u0026\u0026 foo.a.b.c;\n*}\n*✅ 正确例子 \n\n*type T = {\n*  a?: {\n*    b?: {\n*      c: string;\n*      method?: () =\u003e void;\n*    };\n*  };\n*};\n*\n*function myFunc(foo: T | null) {\n*  return foo?.a?.b?.c;\n*}\n\n*eslint 规则\n*@typescript-eslint/prefer-optional-chain\n*不可变的私有属性标记成 readonly \n为了避免后期迭代维护的时候对私有属性非预期的更改导致原有代码逻辑改变，应该将目前代码中不可变的私有属性标记为 readonly。\n*❌ 错误例子 \n\n*class Container {\n*  // These member variables could be marked as readonly\n*  private neverModifiedMember = true;\n*  private onlyModifiedInConstructor: number;\n*\n*  public constructor(\n*    onlyModifiedInConstructor: number,\n*    // Private parameter properties can also be marked as readonly\n*    private neverModifiedParameter: string,\n*  ) {\n*    this.onlyModifiedInConstructor = onlyModifiedInConstructor;\n*  }\n*}\n*✅ 正确例子 \n\n*class Container {\n*  // Public members might be modified externally\n*  public publicMember: boolean;\n*\n*  private readonly onlyModifiedInConstructor: number;\n*\n*  // Protected members might be modified by child classes\n*  protected protectedMember: number;\n*\n*  // This is modified later on by the class\n*  private modifiedLater = 'unchanged';\n*\n*  constructor() {\n*    this. onlyModifiedInConstructor = 1;\n*  }\n*\n*  public mutate() {\n*    this.modifiedLater = 'mutated';\n*  }\n*}\n*eslint 规则\n*@typescript-eslint/prefer-readonly\n\n"
}
    //  \n\n换成\n的正则是
    // function replace_newline($str) {
    //     $str = preg_replace("/\r\n/", "\n", $str);
    //     $str = preg_replace("/\r/", "\n", $str);
    //     $str = preg_replace("/\n{2,}/", "\n", $str);
    //     return $str;
    // }
    function test2(str) {
      return str.replace(/\n+/g, '\n');
    }
    function test (str) {
      return str.replace(/\n\*/g, '\n');
    }
    const res1 = test(typescript.text)
    console.log(res1)
    document.body.innerText = test2(res1);
  </script>
</body>
</html>